Skip to content

Commit 97d5633

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

File tree

4 files changed

+101
-9
lines changed

4 files changed

+101
-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: 88 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,15 +37,18 @@ 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

4954
const GitCustomCAFile = "/tmp/vp-git-cas.pem"
@@ -176,7 +181,7 @@ func checkoutRevision(fullClient kubernetes.Interface, gitOps GitOperations, url
176181
if repo == nil { // we mocked the above OpenRepository
177182
return nil
178183
}
179-
foptions, err := getFetchOptions(url, secret)
184+
foptions, err := getFetchOptions(fullClient, url, secret)
180185
if err != nil {
181186
return err
182187
}
@@ -235,7 +240,7 @@ func cloneRepo(fullClient kubernetes.Interface, gitOps GitOperations, url, direc
235240
}
236241
fmt.Printf("git clone %s into %s\n", url, directory)
237242

238-
options, err := getCloneOptions(url, secret)
243+
options, err := getCloneOptions(fullClient, url, secret)
239244
if err != nil {
240245
return err
241246
}
@@ -259,7 +264,7 @@ func cloneRepo(fullClient kubernetes.Interface, gitOps GitOperations, url, direc
259264
return nil
260265
}
261266

262-
func getFetchOptions(url string, secret map[string][]byte) (*git.FetchOptions, error) {
267+
func getFetchOptions(fullClient kubernetes.Interface, url string, secret map[string][]byte) (*git.FetchOptions, error) {
263268
var foptions = &git.FetchOptions{
264269
RemoteName: "origin",
265270
Force: true,
@@ -275,12 +280,19 @@ func getFetchOptions(url string, secret map[string][]byte) (*git.FetchOptions, e
275280
return nil, err
276281
}
277282
foptions.Auth = publicKey
283+
case GitAuthGitHubApp:
284+
gitHubAppAuth, err := getGitHubAppAuth(fullClient, secret)
285+
if err != nil {
286+
return nil, err
287+
}
288+
289+
foptions.Auth = gitHubAppAuth
278290
}
279291

280292
return foptions, nil
281293
}
282294

283-
func getCloneOptions(url string, secret map[string][]byte) (*git.CloneOptions, error) {
295+
func getCloneOptions(fullClient kubernetes.Interface, url string, secret map[string][]byte) (*git.CloneOptions, error) {
284296
// Clone the given repository to the given directory
285297
var options = &git.CloneOptions{
286298
URL: url,
@@ -300,6 +312,13 @@ func getCloneOptions(url string, secret map[string][]byte) (*git.CloneOptions, e
300312
return nil, err
301313
}
302314
options.Auth = publicKey
315+
case GitAuthGitHubApp:
316+
gitHubAppAuth, err := getGitHubAppAuth(fullClient, secret)
317+
if err != nil {
318+
return nil, err
319+
}
320+
321+
options.Auth = gitHubAppAuth
303322
}
304323

305324
return options, nil
@@ -333,6 +352,55 @@ func getSshPublicKey(url string, secret map[string][]byte) (*ssh.PublicKeys, err
333352
return publicKey, nil
334353
}
335354

355+
func getGitHubAppAuth(fullClient kubernetes.Interface, secret map[string][]byte) (*http.BasicAuth, error) {
356+
357+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
358+
defer cancel()
359+
360+
baseURL := "https://api.github.com"
361+
362+
if githubAppEnterpriseBaseUrl := getField(secret, "username"); githubAppEnterpriseBaseUrl != nil {
363+
baseURL = strings.TrimSuffix(string(githubAppEnterpriseBaseUrl), "/")
364+
}
365+
366+
transport := getHTTPSTransport(fullClient)
367+
368+
githubAppID, err := IntOrZero(secret, "githubAppID")
369+
if err != nil {
370+
return nil, err
371+
}
372+
373+
githubAppInstallationID, err := IntOrZero(secret, "githubAppInstallationID")
374+
if err != nil {
375+
return nil, err
376+
}
377+
378+
itr, err := ghinstallation.New(transport,
379+
githubAppID,
380+
githubAppInstallationID,
381+
getField(secret, "githubAppPrivateKey"),
382+
)
383+
384+
if err != nil {
385+
return nil, fmt.Errorf("failed to initialize GitHub installation transport: %w", err)
386+
}
387+
388+
itr.BaseURL = baseURL
389+
390+
accessToken, err := itr.Token(ctx)
391+
if err != nil {
392+
return nil, fmt.Errorf("could not get GitHub App installation token: %w", err)
393+
}
394+
395+
auth := &http.BasicAuth{
396+
Username: "x-access-token",
397+
Password: accessToken,
398+
}
399+
400+
return auth, nil
401+
402+
}
403+
336404
// This returns the user prefix in git urls like:
337405
// git@github.com:/foo/bar or "" when not found
338406
func getUserFromURL(url string) string {
@@ -362,14 +430,27 @@ func repoHash(directory string) (string, error) {
362430
// if a secret has
363431
// returns "" if a secret could not be parse, "ssh" if it is an ssh auth, and "password" if a username + pass auth
364432
func detectGitAuthType(secret map[string][]byte) GitAuthenticationBackend {
433+
// SSH
365434
if _, ok := secret["sshPrivateKey"]; ok {
366435
return GitAuthSsh
367436
}
437+
438+
// Username + Password
368439
_, hasUser := secret["username"]
369440
_, hasPassword := secret["password"]
370441
if hasUser && hasPassword {
371442
return GitAuthPassword
372443
}
444+
445+
// GitHub App
446+
_, hasGithubAppID := secret["githubAppID"]
447+
_, hasGithubAppInstallationID := secret["githubAppInstallationID"]
448+
_, hasGithubAppPrivateKey := secret["githubAppPrivateKey"]
449+
if hasGithubAppID && hasGithubAppInstallationID && hasGithubAppPrivateKey {
450+
return GitAuthGitHubApp
451+
}
452+
453+
// None
373454
return GitAuthNone
374455
}
375456

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)