Skip to content

Commit 7d93c7a

Browse files
authored
feat: add support for separate GitHub app credentials (#649)
* feat: add support for separate GitHub app credentials stored as Kubernetes secrets Signed-off-by: Dustin Lactin <dustin.lactin@gmail.com> * test: added tests for consuming GitHub app credentials from a secret Signed-off-by: Dustin Lactin <dustin.lactin@gmail.com> * fix: added GitHub App placeholder words to expect.txt Signed-off-by: Dustin Lactin <dustin.lactin@gmail.com> * fix: checking for errors when converting GitHub App and Installation IDs Signed-off-by: Dustin Lactin <dustin.lactin@gmail.com> * fix: added more descriptive error messages for string-to-number conversions Signed-off-by: Dustin Lactin <dustin.lactin@gmail.com> --------- Signed-off-by: Dustin Lactin <dustin.lactin@gmail.com>
1 parent 0252dae commit 7d93c7a

File tree

4 files changed

+84
-10
lines changed

4 files changed

+84
-10
lines changed

.github/actions/spelling/expect.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
aeece
33
Artifactory
4+
applicationid
45
bacd
56
CVE
67
credref
@@ -11,9 +12,11 @@ fbd
1112
ffb
1213
gitlab
1314
helmvalues
15+
installationid
1416
jfrog
1517
mep
1618
myregistry
19+
PRIVATEKEYDATA
1720
repocreds
1821
rollbacked
1922
someimage

docs/basics/update-methods.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ Example:
123123
argocd-image-updater.argoproj.io/write-back-method: git:secret:argocd-image-updater/git-creds
124124
```
125125

126-
If the repository is accessed using HTTPS, the secret must contain two fields:
126+
If the repository is accessed using HTTPS, the secret must contain either user credentials or GitHub app credentials.
127+
128+
If the repository is accessed using user credentials, the secret requires two fields
127129
`username` which holds the Git username, and `password` which holds the user's
128130
password or a private access token (PAT) with write access to the repository.
129131
You can generate such a secret using `kubectl`, e.g.:
@@ -134,6 +136,16 @@ kubectl -n argocd-image-updater create secret generic git-creds \
134136
--from-literal=password=somepassword
135137
```
136138

139+
If the repository is accessed using GitHub app credentials, the secret requires three fields `githubAppID` which holds the GitHub Application ID, `githubAppInstallationID` which holds the GitHub Organization Installation ID, and `githubAppPrivateKey` which holds the GitHub Application private key. The GitHub Application must be installed into the target repository with write access.
140+
You can generate such a secret using `kubectl`, e.g.:
141+
142+
```bash
143+
kubectl -n argocd-image-updater create secret generic git-creds \
144+
--from-literal=githubAppID=applicationid \
145+
--from-literal=githubAppInstallationID=installationid \
146+
--from-literal=githubAppPrivateKey='-----BEGIN RSA PRIVATE KEY-----PRIVATEKEYDATA-----END RSA PRIVATE KEY-----'
147+
```
148+
137149
If the repository is accessed using SSH, the secret must contain the field
138150
`sshPrivateKey`, which holds a SSH private key in OpenSSH-compatible PEM
139151
format. To create such a secret from an existing private key, you can use

pkg/argocd/gitcreds.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package argocd
33
import (
44
"context"
55
"fmt"
6+
"strconv"
67
"strings"
78

89
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -66,14 +67,31 @@ func getCredsFromSecret(wbc *WriteBackConfig, credentialsSecret string, kubeClie
6667
}
6768
return git.NewSSHCreds(string(sshPrivateKey), "", true), nil
6869
} else if git.IsHTTPSURL(wbc.GitRepo) {
69-
var username, password []byte
70-
if username, ok = credentials["username"]; !ok {
71-
return nil, fmt.Errorf("invalid secret %s: does not contain field username", credentialsSecret)
70+
var username, password, githubAppID, githubAppInstallationID, githubAppPrivateKey []byte
71+
if githubAppID, ok = credentials["githubAppID"]; ok {
72+
if githubAppInstallationID, ok = credentials["githubAppInstallationID"]; !ok {
73+
return nil, fmt.Errorf("invalid secret %s: does not contain field githubAppInstallationID", credentialsSecret)
74+
}
75+
if githubAppPrivateKey, ok = credentials["githubAppPrivateKey"]; !ok {
76+
return nil, fmt.Errorf("invalid secret %s: does not contain field githubAppPrivateKey", credentialsSecret)
77+
}
78+
// converting byte array to string and ultimately int64 for NewGitHubAppCreds
79+
intGithubAppID, err := strconv.ParseInt(string(githubAppID), 10, 64)
80+
if err != nil {
81+
return nil, fmt.Errorf("invalid value in field githubAppID: %w", err)
82+
}
83+
intGithubAppInstallationID, _ := strconv.ParseInt(string(githubAppInstallationID), 10, 64)
84+
if err != nil {
85+
return nil, fmt.Errorf("invalid value in field githubAppInstallationID: %w", err)
86+
}
87+
return git.NewGitHubAppCreds(intGithubAppID, intGithubAppInstallationID, string(githubAppPrivateKey), "", "", "", "", true), nil
88+
} else if username, ok = credentials["username"]; ok {
89+
if password, ok = credentials["password"]; !ok {
90+
return nil, fmt.Errorf("invalid secret %s: does not contain field password", credentialsSecret)
91+
}
92+
return git.NewHTTPSCreds(string(username), string(password), "", "", true, ""), nil
7293
}
73-
if password, ok = credentials["password"]; !ok {
74-
return nil, fmt.Errorf("invalid secret %s: does not contain field password", credentialsSecret)
75-
}
76-
return git.NewHTTPSCreds(string(username), string(password), "", "", true, ""), nil
94+
return nil, fmt.Errorf("invalid repository credentials in secret %s: does not contain githubAppID or username", credentialsSecret)
7795
}
7896
return nil, fmt.Errorf("unknown repository type")
7997
}

pkg/argocd/update_test.go

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,7 +2055,7 @@ func Test_GetWriteBackConfig(t *testing.T) {
20552055
}
20562056

20572057
func Test_GetGitCreds(t *testing.T) {
2058-
t.Run("HTTP creds from a secret", func(t *testing.T) {
2058+
t.Run("HTTP user creds from a secret", func(t *testing.T) {
20592059
argoClient := argomock.ArgoCD{}
20602060
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
20612061
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
@@ -2090,11 +2090,52 @@ func Test_GetGitCreds(t *testing.T) {
20902090
creds, err := wbc.GetCreds(&app)
20912091
require.NoError(t, err)
20922092
require.NotNil(t, creds)
2093-
// Must have HTTPS creds
2093+
// Must have HTTPS user creds
20942094
_, ok := creds.(git.HTTPSCreds)
20952095
require.True(t, ok)
20962096
})
20972097

2098+
t.Run("HTTP GitHub App creds from a secret", func(t *testing.T) {
2099+
argoClient := argomock.ArgoCD{}
2100+
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
2101+
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
2102+
"githubAppID": []byte("12345678"),
2103+
"githubAppInstallationID": []byte("87654321"),
2104+
"githubAppPrivateKey": []byte("foo"),
2105+
})
2106+
kubeClient := kube.KubernetesClient{
2107+
Clientset: fake.NewFakeClientsetWithResources(secret),
2108+
}
2109+
app := v1alpha1.Application{
2110+
ObjectMeta: v1.ObjectMeta{
2111+
Name: "testapp",
2112+
Annotations: map[string]string{
2113+
"argocd-image-updater.argoproj.io/image-list": "nginx",
2114+
"argocd-image-updater.argoproj.io/write-back-method": "git:secret:argocd-image-updater/git-creds",
2115+
"argocd-image-updater.argoproj.io/git-credentials": "argocd-image-updater/git-creds",
2116+
},
2117+
},
2118+
Spec: v1alpha1.ApplicationSpec{
2119+
Source: &v1alpha1.ApplicationSource{
2120+
RepoURL: "https://example.com/example",
2121+
TargetRevision: "main",
2122+
},
2123+
},
2124+
Status: v1alpha1.ApplicationStatus{
2125+
SourceType: v1alpha1.ApplicationSourceTypeKustomize,
2126+
},
2127+
}
2128+
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
2129+
require.NoError(t, err)
2130+
2131+
creds, err := wbc.GetCreds(&app)
2132+
require.NoError(t, err)
2133+
require.NotNil(t, creds)
2134+
// Must have HTTPS GitHub App creds
2135+
_, ok := creds.(git.GitHubAppCreds)
2136+
require.True(t, ok)
2137+
})
2138+
20982139
t.Run("SSH creds from a secret", func(t *testing.T) {
20992140
argoClient := argomock.ArgoCD{}
21002141
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)

0 commit comments

Comments
 (0)