Skip to content

Commit dbee938

Browse files
committed
Added support for GitHub App auth
Signed-off-by: Andrew Block <andy.block@gmail.com>
1 parent 1bb7049 commit dbee938

File tree

5 files changed

+313
-9
lines changed

5 files changed

+313
-9
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
code.gitea.io/sdk/gitea v0.22.1
77
github.com/Masterminds/semver/v3 v3.4.0
88
github.com/argoproj-labs/argocd-operator v0.15.0
9+
github.com/bradleyfalzon/ghinstallation/v2 v2.17.0
910
github.com/go-errors/errors v1.5.1
1011
github.com/go-git/go-git/v5 v5.16.4
1112
github.com/go-logr/logr v1.4.3
@@ -51,7 +52,6 @@ require (
5152
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
5253
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
5354
github.com/bombsimon/logrusr/v4 v4.1.0 // indirect
54-
github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 // indirect
5555
github.com/casbin/casbin/v2 v2.127.0 // indirect
5656
github.com/casbin/govaluate v1.10.0 // indirect
5757
github.com/cespare/xxhash/v2 v2.3.0 // indirect

hack/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ GIT_VERSION=$(git describe --always --tags || true)
55
VERSION=${CI_UPSTREAM_VERSION:-${GIT_VERSION}}
66
GIT_COMMIT=$(git rev-list -1 HEAD || true)
77
COMMIT=${CI_UPSTREAM_COMMIT:-${GIT_COMMIT}}
8-
BUILD_DATE=$(date --utc -Iseconds)
8+
BUILD_DATE=$(TZ=UTC date -Iseconds)
99

1010
LDFLAGS="-s -w "
1111
REPO="github.com/hybrid-cloud-patterns/patterns-operator"

internal/controller/checkout.go

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ limitations under the License.
1717
package controllers
1818

1919
import (
20+
"context"
2021
"fmt"
2122
nethttp "net/http"
2223
"os"
2324
"regexp"
2425
"strings"
26+
"time"
2527

2628
"path/filepath"
2729

@@ -35,17 +37,21 @@ import (
3537
"github.com/go-git/go-git/v5/plumbing/transport/http"
3638
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
3739

40+
"github.com/bradleyfalzon/ghinstallation/v2"
41+
3842
argogit "github.com/argoproj/argo-cd/v3/util/git"
3943
)
4044

4145
type GitAuthenticationBackend uint
4246

4347
const (
44-
GitAuthNone GitAuthenticationBackend = 0
45-
GitAuthPassword GitAuthenticationBackend = 1
46-
GitAuthSsh GitAuthenticationBackend = 2
48+
GitAuthNone GitAuthenticationBackend = 0
49+
GitAuthPassword GitAuthenticationBackend = 1
50+
GitAuthSsh GitAuthenticationBackend = 2
51+
GitAuthGitHubApp GitAuthenticationBackend = 3
4752
)
4853

54+
const ContextTimeoutSeconds = 15 * time.Second
4955
const GitCustomCAFile = "/tmp/vp-git-cas.pem"
5056
const GitHEAD = "HEAD"
5157
const VPTmpFolder = "vp"
@@ -176,7 +182,7 @@ func checkoutRevision(fullClient kubernetes.Interface, gitOps GitOperations, url
176182
if repo == nil { // we mocked the above OpenRepository
177183
return nil
178184
}
179-
foptions, err := getFetchOptions(url, secret)
185+
foptions, err := getFetchOptions(fullClient, url, secret)
180186
if err != nil {
181187
return err
182188
}
@@ -235,7 +241,7 @@ func cloneRepo(fullClient kubernetes.Interface, gitOps GitOperations, url, direc
235241
}
236242
fmt.Printf("git clone %s into %s\n", url, directory)
237243

238-
options, err := getCloneOptions(url, secret)
244+
options, err := getCloneOptions(fullClient, url, secret)
239245
if err != nil {
240246
return err
241247
}
@@ -259,7 +265,7 @@ func cloneRepo(fullClient kubernetes.Interface, gitOps GitOperations, url, direc
259265
return nil
260266
}
261267

262-
func getFetchOptions(url string, secret map[string][]byte) (*git.FetchOptions, error) {
268+
func getFetchOptions(fullClient kubernetes.Interface, url string, secret map[string][]byte) (*git.FetchOptions, error) {
263269
var foptions = &git.FetchOptions{
264270
RemoteName: "origin",
265271
Force: true,
@@ -275,12 +281,19 @@ func getFetchOptions(url string, secret map[string][]byte) (*git.FetchOptions, e
275281
return nil, err
276282
}
277283
foptions.Auth = publicKey
284+
case GitAuthGitHubApp:
285+
gitHubAppAuth, err := getGitHubAppAuth(fullClient, secret)
286+
if err != nil {
287+
return nil, err
288+
}
289+
290+
foptions.Auth = gitHubAppAuth
278291
}
279292

280293
return foptions, nil
281294
}
282295

283-
func getCloneOptions(url string, secret map[string][]byte) (*git.CloneOptions, error) {
296+
func getCloneOptions(fullClient kubernetes.Interface, url string, secret map[string][]byte) (*git.CloneOptions, error) {
284297
// Clone the given repository to the given directory
285298
var options = &git.CloneOptions{
286299
URL: url,
@@ -300,6 +313,13 @@ func getCloneOptions(url string, secret map[string][]byte) (*git.CloneOptions, e
300313
return nil, err
301314
}
302315
options.Auth = publicKey
316+
case GitAuthGitHubApp:
317+
gitHubAppAuth, err := getGitHubAppAuth(fullClient, secret)
318+
if err != nil {
319+
return nil, err
320+
}
321+
322+
options.Auth = gitHubAppAuth
303323
}
304324

305325
return options, nil
@@ -333,6 +353,62 @@ func getSshPublicKey(url string, secret map[string][]byte) (*ssh.PublicKeys, err
333353
return publicKey, nil
334354
}
335355

356+
func getGitHubAppAuthTransport(fullClient kubernetes.Interface, secret map[string][]byte) (*ghinstallation.Transport, error) {
357+
baseURL := "https://api.github.com"
358+
359+
if githubAppEnterpriseBaseUrl := getField(secret, "githubAppEnterpriseBaseUrl"); githubAppEnterpriseBaseUrl != nil {
360+
baseURL = strings.TrimSuffix(string(githubAppEnterpriseBaseUrl), "/")
361+
}
362+
363+
transport := getHTTPSTransport(fullClient)
364+
365+
githubAppID, err := IntOrZero(secret, "githubAppID")
366+
if err != nil {
367+
return nil, err
368+
}
369+
370+
githubAppInstallationID, err := IntOrZero(secret, "githubAppInstallationID")
371+
if err != nil {
372+
return nil, err
373+
}
374+
375+
itr, err := ghinstallation.New(transport,
376+
githubAppID,
377+
githubAppInstallationID,
378+
getField(secret, "githubAppPrivateKey"),
379+
)
380+
381+
if err != nil {
382+
return nil, fmt.Errorf("failed to initialize GitHub installation transport: %w", err)
383+
}
384+
385+
itr.BaseURL = baseURL
386+
387+
return itr, nil
388+
}
389+
390+
func getGitHubAppAuth(fullClient kubernetes.Interface, secret map[string][]byte) (*http.BasicAuth, error) {
391+
ctx, cancel := context.WithTimeout(context.Background(), ContextTimeoutSeconds)
392+
defer cancel()
393+
394+
// Obtain GitHub Transport
395+
itr, err := getGitHubAppAuthTransport(fullClient, secret)
396+
if err != nil {
397+
return nil, err
398+
}
399+
accessToken, err := itr.Token(ctx)
400+
if err != nil {
401+
return nil, fmt.Errorf("could not get GitHub App installation token: %w", err)
402+
}
403+
404+
auth := &http.BasicAuth{
405+
Username: "x-access-token",
406+
Password: accessToken,
407+
}
408+
409+
return auth, nil
410+
}
411+
336412
// This returns the user prefix in git urls like:
337413
// git@github.com:/foo/bar or "" when not found
338414
func getUserFromURL(url string) string {
@@ -362,14 +438,27 @@ func repoHash(directory string) (string, error) {
362438
// if a secret has
363439
// returns "" if a secret could not be parse, "ssh" if it is an ssh auth, and "password" if a username + pass auth
364440
func detectGitAuthType(secret map[string][]byte) GitAuthenticationBackend {
441+
// SSH
365442
if _, ok := secret["sshPrivateKey"]; ok {
366443
return GitAuthSsh
367444
}
445+
446+
// Username + Password
368447
_, hasUser := secret["username"]
369448
_, hasPassword := secret["password"]
370449
if hasUser && hasPassword {
371450
return GitAuthPassword
372451
}
452+
453+
// GitHub App
454+
_, hasGithubAppID := secret["githubAppID"]
455+
_, hasGithubAppInstallationID := secret["githubAppInstallationID"]
456+
_, hasGithubAppPrivateKey := secret["githubAppPrivateKey"]
457+
if hasGithubAppID && hasGithubAppInstallationID && hasGithubAppPrivateKey {
458+
return GitAuthGitHubApp
459+
}
460+
461+
// None
373462
return GitAuthNone
374463
}
375464

internal/controller/utils.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"os"
2828
"path"
2929
"path/filepath"
30+
"strconv"
3031
"strings"
3132

3233
"crypto/rand"
@@ -404,3 +405,13 @@ func IsCommonSlimmed(patternPath string) bool {
404405
}
405406
return true
406407
}
408+
409+
// IntOrZero retrieves an integer value from a map by key.
410+
func IntOrZero(secret map[string][]byte, key string) (int64, error) {
411+
val, present := secret[key]
412+
if !present {
413+
return 0, nil
414+
}
415+
416+
return strconv.ParseInt(string(val), 10, 64)
417+
}

0 commit comments

Comments
 (0)