diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 642a1bc4..66286858 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -2,6 +2,9 @@
aeece
Artifactory
applicationid
+atlassian
+Bitbucket
+bitbucketserver
bacd
CVE
credref
@@ -11,11 +14,14 @@ eec
fbd
ffb
gitlab
+GPG
helmvalues
+html
installationid
jfrog
mep
myregistry
+openpgp
PRIVATEKEYDATA
repocreds
rollbacked
diff --git a/cmd/main.go b/cmd/main.go
index de6f0c69..81eed761 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -28,25 +28,28 @@ const applicationsAPIKindArgoCD = "argocd"
// ImageUpdaterConfig contains global configuration and required runtime data
type ImageUpdaterConfig struct {
- ApplicationsAPIKind string
- ClientOpts argocd.ClientOptions
- ArgocdNamespace string
- DryRun bool
- CheckInterval time.Duration
- ArgoClient argocd.ArgoCD
- LogLevel string
- KubeClient *kube.KubernetesClient
- MaxConcurrency int
- HealthPort int
- MetricsPort int
- RegistriesConf string
- AppNamePatterns []string
- AppLabel string
- GitCommitUser string
- GitCommitMail string
- GitCommitMessage *template.Template
- DisableKubeEvents bool
- GitCreds git.CredsStore
+ ApplicationsAPIKind string
+ ClientOpts argocd.ClientOptions
+ ArgocdNamespace string
+ DryRun bool
+ CheckInterval time.Duration
+ ArgoClient argocd.ArgoCD
+ LogLevel string
+ KubeClient *kube.KubernetesClient
+ MaxConcurrency int
+ HealthPort int
+ MetricsPort int
+ RegistriesConf string
+ AppNamePatterns []string
+ AppLabel string
+ GitCommitUser string
+ GitCommitMail string
+ GitCommitMessage *template.Template
+ GitCommitSigningKey string
+ GitCommitSigningMethod string
+ GitCommitSignOff bool
+ DisableKubeEvents bool
+ GitCreds git.CredsStore
}
// newRootCommand implements the root command of argocd-image-updater
diff --git a/cmd/run.go b/cmd/run.go
index bcc0e1fe..751c29c9 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -240,6 +240,9 @@ func newRunCommand() *cobra.Command {
runCmd.Flags().BoolVar(&warmUpCache, "warmup-cache", true, "whether to perform a cache warm-up on startup")
runCmd.Flags().StringVar(&cfg.GitCommitUser, "git-commit-user", env.GetStringVal("GIT_COMMIT_USER", "argocd-image-updater"), "Username to use for Git commits")
runCmd.Flags().StringVar(&cfg.GitCommitMail, "git-commit-email", env.GetStringVal("GIT_COMMIT_EMAIL", "noreply@argoproj.io"), "E-Mail address to use for Git commits")
+ runCmd.Flags().StringVar(&cfg.GitCommitSigningKey, "git-commit-signing-key", env.GetStringVal("GIT_COMMIT_SIGNING_KEY", ""), "GnuPG key ID or path to Private SSH Key used to sign the commits")
+ runCmd.Flags().StringVar(&cfg.GitCommitSigningMethod, "git-commit-signing-method", env.GetStringVal("GIT_COMMIT_SIGNING_METHOD", "openpgp"), "Method used to sign Git commits ('openpgp' or 'ssh')")
+ runCmd.Flags().BoolVar(&cfg.GitCommitSignOff, "git-commit-sign-off", env.GetBoolVal("GIT_COMMIT_SIGN_OFF", false), "Whether to sign-off git commits")
runCmd.Flags().StringVar(&commitMessagePath, "git-commit-message-path", defaultCommitTemplatePath, "Path to a template to use for Git commit messages")
runCmd.Flags().BoolVar(&cfg.DisableKubeEvents, "disable-kube-events", env.GetBoolVal("IMAGE_UPDATER_KUBE_EVENTS", false), "Disable kubernetes events")
@@ -319,16 +322,19 @@ func runImageUpdater(cfg *ImageUpdaterConfig, warmUp bool) (argocd.ImageUpdaterR
defer sem.Release(1)
log.Debugf("Processing application %s", app)
upconf := &argocd.UpdateConfiguration{
- NewRegFN: registry.NewClient,
- ArgoClient: cfg.ArgoClient,
- KubeClient: cfg.KubeClient,
- UpdateApp: &curApplication,
- DryRun: dryRun,
- GitCommitUser: cfg.GitCommitUser,
- GitCommitEmail: cfg.GitCommitMail,
- GitCommitMessage: cfg.GitCommitMessage,
- DisableKubeEvents: cfg.DisableKubeEvents,
- GitCreds: cfg.GitCreds,
+ NewRegFN: registry.NewClient,
+ ArgoClient: cfg.ArgoClient,
+ KubeClient: cfg.KubeClient,
+ UpdateApp: &curApplication,
+ DryRun: dryRun,
+ GitCommitUser: cfg.GitCommitUser,
+ GitCommitEmail: cfg.GitCommitMail,
+ GitCommitMessage: cfg.GitCommitMessage,
+ GitCommitSigningKey: cfg.GitCommitSigningKey,
+ GitCommitSigningMethod: cfg.GitCommitSigningMethod,
+ GitCommitSignOff: cfg.GitCommitSignOff,
+ DisableKubeEvents: cfg.DisableKubeEvents,
+ GitCreds: cfg.GitCreds,
}
res := argocd.UpdateApplication(upconf, syncState)
result.NumApplicationsProcessed += 1
diff --git a/docs/basics/update-methods.md b/docs/basics/update-methods.md
index 404b5aa4..73d82bf7 100644
--- a/docs/basics/update-methods.md
+++ b/docs/basics/update-methods.md
@@ -247,6 +247,58 @@ as the author. You can override the author using the
`git.user` and `git.email`
in the `argocd-image-updater-config` ConfigMap.
+## Enabling commit signature signing using an SSH or GPG key
+
+### 1. SCM branch protection rules require signed commits
+Commit signing for SCM branch protection rules require the repository be accessed using HTTPS or SSH with a user account.
+Repositories accessed using a GitHub App can not be verified when using the git command line at this time.
+
+Each Git commit associated with an author's name and email address can be signed via a private SSH key or GPG key.
+
+Commit signing requires a bot account with a GPG or SSH key and the username and email address configured to match the bot account.
+
+Your preferred signing key must be associated with your bot account. See SCM provider documentation for further details:
+* [GitHub](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
+* [GitLab](https://docs.gitlab.com/ee/user/project/repository/signed_commits/)
+* [Bitbucket](https://confluence.atlassian.com/bitbucketserver/controlling-access-to-code-776639770.html)
+
+### 2. Signing commits for future use with ArgoCD Source Verification Policies
+Commits can also be signed for use with source verification.
+In this case signing keys do not need to be associated with an SCM user account.
+
+**SSH:**
+
+The private key must be mounted and accessible on the `argocd-image-updater` pod.
+
+Set `git.commit-signing-key` `argocd-image-updater-config` ConfigMap to the path of your private key:
+
+```yaml
+data:
+ git.commit-sign-off: "true"
+ git.commit-signing-key: /app/.ssh/id_rsa
+ git.commit-signing-method: "ssh"
+```
+
+Create a new SSH secret or use your existing SSH secret:
+```bash
+kubectl -n argocd-image-updater create secret generic ssh-git-creds \
+ --from-file=sshPrivateKey=~/.ssh/id_rsa
+```
+
+**GPG:**
+
+The GPG private key must be installed and available in the `argocd-image-updater` pod.
+The `git.commit-signing-method` defaults to `openpgp`.
+Set `git.commit-signing-key` in the `argocd-image-updater-config` ConfigMap to the GPG key ID you want to use:
+
+```yaml
+data:
+ git.commit-sign-off: "true"
+ git.commit-signing-key: 3AA5C34371567BD2
+```
+
+#### Commit Sign Off can be enabled by setting `git.commit-sign-off: "true"`
+
### Changing the Git commit message
You can change the default commit message used by Argo CD Image Updater to some
diff --git a/ext/git/mocks/Client.go b/ext/git/mocks/Client.go
index 63252281..87bd3b46 100644
--- a/ext/git/mocks/Client.go
+++ b/ext/git/mocks/Client.go
@@ -160,6 +160,20 @@ func (_m *Client) Config(username string, email string) error {
return r0
}
+// SigningConfig provides a mock function with given fields: signingkey
+func (_m *Client) SigningConfig(signingkey string) error {
+ ret := _m.Called(signingkey)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(signingkey)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
// Fetch provides a mock function with given fields: revision
func (_m *Client) Fetch(revision string) error {
ret := _m.Called(revision)
diff --git a/ext/git/writer.go b/ext/git/writer.go
index 7b84ff92..94535aec 100644
--- a/ext/git/writer.go
+++ b/ext/git/writer.go
@@ -14,8 +14,10 @@ type CommitOptions struct {
CommitMessageText string
// CommitMessagePath holds the path to a file to be used for the commit message (-F option)
CommitMessagePath string
- // SigningKey holds a GnuPG key ID used to sign the commit with (-S option)
+ // SigningKey holds a GnuPG key ID or path to a Private SSH Key used to sign the commit with (-S option)
SigningKey string
+ // SigningMethod holds the signing method used to sign commits. (git -c gpg.format=ssh option)
+ SigningMethod string
// SignOff specifies whether to sign-off a commit (-s option)
SignOff bool
}
@@ -25,16 +27,18 @@ type CommitOptions struct {
// changes will be commited. If message is not the empty string, it will be
// used as the commit message, otherwise a default commit message will be used.
// If signingKey is not the empty string, commit will be signed with the given
-// GPG key.
+// GPG or SSH key.
func (m *nativeGitClient) Commit(pathSpec string, opts *CommitOptions) error {
defaultCommitMsg := "Update parameters"
- args := []string{"commit"}
+ // Git configuration
+ config := "gpg.format=" + opts.SigningMethod
+ args := []string{"-c", config, "commit"}
if pathSpec == "" || pathSpec == "*" {
args = append(args, "-a")
}
- if opts.SigningKey != "" {
- args = append(args, "-S", opts.SigningKey)
- }
+ // Commit fails with a space between -S flag and path to SSH key
+ // -S/user/test/.ssh/signingKey or -SAAAAAAAA...
+ args = append(args, fmt.Sprintf("-S%s", opts.SigningKey))
if opts.SignOff {
args = append(args, "-s")
}
diff --git a/manifests/base/deployment/argocd-image-updater-deployment.yaml b/manifests/base/deployment/argocd-image-updater-deployment.yaml
index 5682ed78..24bd7ced 100644
--- a/manifests/base/deployment/argocd-image-updater-deployment.yaml
+++ b/manifests/base/deployment/argocd-image-updater-deployment.yaml
@@ -77,6 +77,24 @@ spec:
name: argocd-image-updater-config
key: git.email
optional: true
+ - name: GIT_COMMIT_SIGNING_KEY
+ valueFrom:
+ configMapKeyRef:
+ key: git.commit-signing-key
+ name: argocd-image-updater-config
+ optional: true
+ - name: GIT_COMMIT_SIGNING_METHOD
+ valueFrom:
+ configMapKeyRef:
+ key: git.commit-signing-method
+ name: argocd-image-updater-config
+ optional: true
+ - name: GIT_COMMIT_SIGN_OFF
+ valueFrom:
+ configMapKeyRef:
+ key: git.commit-sign-off
+ name: argocd-image-updater-config
+ optional: true
- name: IMAGE_UPDATER_KUBE_EVENTS
valueFrom:
configMapKeyRef:
@@ -116,6 +134,10 @@ spec:
name: ssh-config
- mountPath: /tmp
name: tmp
+ - name: ssh-signing-key
+ mountPath: /app/.ssh/id_rsa
+ readOnly: true
+ subPath: sshPrivateKey
serviceAccountName: argocd-image-updater
volumes:
- configMap:
@@ -135,5 +157,9 @@ spec:
name: argocd-image-updater-ssh-config
optional: true
name: ssh-config
+ - name: ssh-signing-key
+ secret:
+ secretName: ssh-git-creds
+ optional: true
- emptyDir: {}
name: tmp
diff --git a/manifests/install.yaml b/manifests/install.yaml
index b65b12bd..97167ec0 100644
--- a/manifests/install.yaml
+++ b/manifests/install.yaml
@@ -158,6 +158,24 @@ spec:
key: git.email
name: argocd-image-updater-config
optional: true
+ - name: GIT_COMMIT_SIGNING_KEY
+ valueFrom:
+ configMapKeyRef:
+ key: git.commit-signing-key
+ name: argocd-image-updater-config
+ optional: true
+ - name: GIT_COMMIT_SIGNING_METHOD
+ valueFrom:
+ configMapKeyRef:
+ key: git.commit-signing-method
+ name: argocd-image-updater-config
+ optional: true
+ - name: GIT_COMMIT_SIGN_OFF
+ valueFrom:
+ configMapKeyRef:
+ key: git.commit-sign-off
+ name: argocd-image-updater-config
+ optional: true
- name: IMAGE_UPDATER_KUBE_EVENTS
valueFrom:
configMapKeyRef:
@@ -199,6 +217,10 @@ spec:
name: ssh-config
- mountPath: /tmp
name: tmp
+ - mountPath: /app/.ssh/id_rsa
+ name: ssh-signing-key
+ readOnly: true
+ subPath: sshPrivateKey
serviceAccountName: argocd-image-updater
volumes:
- configMap:
@@ -218,5 +240,9 @@ spec:
name: argocd-image-updater-ssh-config
optional: true
name: ssh-config
+ - name: ssh-signing-key
+ optional: true
+ secret:
+ secretName: ssh-git-creds
- emptyDir: {}
name: tmp
diff --git a/pkg/argocd/git.go b/pkg/argocd/git.go
index 71a610a5..1764a454 100644
--- a/pkg/argocd/git.go
+++ b/pkg/argocd/git.go
@@ -234,6 +234,13 @@ func commitChangesGit(app *v1alpha1.Application, wbc *WriteBackConfig, changeLis
defer os.Remove(cm.Name())
}
+ if wbc.GitCommitSigningKey != "" {
+ commitOpts.SigningKey = wbc.GitCommitSigningKey
+ }
+
+ commitOpts.SigningMethod = wbc.GitCommitSigningMethod
+ commitOpts.SignOff = wbc.GitCommitSignOff
+
err = gitC.Commit("", commitOpts)
if err != nil {
return err
diff --git a/pkg/argocd/update.go b/pkg/argocd/update.go
index 98592d8b..3f6f2530 100644
--- a/pkg/argocd/update.go
+++ b/pkg/argocd/update.go
@@ -36,17 +36,20 @@ type ImageUpdaterResult struct {
}
type UpdateConfiguration struct {
- NewRegFN registry.NewRegistryClient
- ArgoClient ArgoCD
- KubeClient *kube.KubernetesClient
- UpdateApp *ApplicationImages
- DryRun bool
- GitCommitUser string
- GitCommitEmail string
- GitCommitMessage *template.Template
- DisableKubeEvents bool
- IgnorePlatforms bool
- GitCreds git.CredsStore
+ NewRegFN registry.NewRegistryClient
+ ArgoClient ArgoCD
+ KubeClient *kube.KubernetesClient
+ UpdateApp *ApplicationImages
+ DryRun bool
+ GitCommitUser string
+ GitCommitEmail string
+ GitCommitMessage *template.Template
+ GitCommitSigningKey string
+ GitCommitSigningMethod string
+ GitCommitSignOff bool
+ DisableKubeEvents bool
+ IgnorePlatforms bool
+ GitCreds git.CredsStore
}
type GitCredsSource func(app *v1alpha1.Application) (git.Creds, error)
@@ -63,17 +66,20 @@ type WriteBackConfig struct {
Method WriteBackMethod
ArgoClient ArgoCD
// If GitClient is not nil, the client will be used for updates. Otherwise, a new client will be created.
- GitClient git.Client
- GetCreds GitCredsSource
- GitBranch string
- GitWriteBranch string
- GitCommitUser string
- GitCommitEmail string
- GitCommitMessage string
- KustomizeBase string
- Target string
- GitRepo string
- GitCreds git.CredsStore
+ GitClient git.Client
+ GetCreds GitCredsSource
+ GitBranch string
+ GitWriteBranch string
+ GitCommitUser string
+ GitCommitEmail string
+ GitCommitMessage string
+ GitCommitSigningKey string
+ GitCommitSigningMethod string
+ GitCommitSignOff bool
+ KustomizeBase string
+ Target string
+ GitRepo string
+ GitCreds git.CredsStore
}
// The following are helper structs to only marshal the fields we require
@@ -330,6 +336,11 @@ func UpdateApplication(updateConf *UpdateConfiguration, state *SyncIterationStat
if len(changeList) > 0 && updateConf.GitCommitMessage != nil {
wbc.GitCommitMessage = TemplateCommitMessage(updateConf.GitCommitMessage, updateConf.UpdateApp.Application.Name, changeList)
}
+ if updateConf.GitCommitSigningKey != "" {
+ wbc.GitCommitSigningKey = updateConf.GitCommitSigningKey
+ }
+ wbc.GitCommitSigningMethod = updateConf.GitCommitSigningMethod
+ wbc.GitCommitSignOff = updateConf.GitCommitSignOff
}
if needUpdate {