Skip to content

Commit 2ada071

Browse files
committed
[RFC-007] Implement GitHub app authentication for git repositories.
- API change to add new `github` provider field in `GitRepository` spec. - Controller change to use the GitHub authentication information specified in `.spec.secretRef` to create the auth options to authenticate to git repositories when the `provider` field is set to `github`, - Tests for new `github` provider field - Updated docs to use GitHub Apps for authentication in source-controller. Signed-off-by: Dipti Pai <[email protected]>
1 parent 53868f7 commit 2ada071

File tree

8 files changed

+106
-19
lines changed

8 files changed

+106
-19
lines changed

api/v1/gitrepository_types.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const (
3535
// GitProviderAzure provides support for authentication to azure
3636
// repositories using Managed Identity.
3737
GitProviderAzure string = "azure"
38+
39+
// GitProviderGitHub provides support for authentication to git
40+
// repositories using GitHub App authentication
41+
GitProviderGitHub string = "github"
3842
)
3943

4044
const (
@@ -88,9 +92,9 @@ type GitRepositorySpec struct {
8892
// +optional
8993
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
9094

91-
// Provider used for authentication, can be 'azure', 'generic'.
95+
// Provider used for authentication, can be 'azure', 'github', 'generic'.
9296
// When not specified, defaults to 'generic'.
93-
// +kubebuilder:validation:Enum=generic;azure
97+
// +kubebuilder:validation:Enum=generic;azure;github
9498
// +optional
9599
Provider string `json:"provider,omitempty"`
96100

config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,12 @@ spec:
105105
type: string
106106
provider:
107107
description: |-
108-
Provider used for authentication, can be 'azure', 'generic'.
108+
Provider used for authentication, can be 'azure', 'github', 'generic'.
109109
When not specified, defaults to 'generic'.
110110
enum:
111111
- generic
112112
- azure
113+
- github
113114
type: string
114115
proxySecretRef:
115116
description: |-

docs/api/v1/source.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ string
390390
</td>
391391
<td>
392392
<em>(Optional)</em>
393-
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;generic&rsquo;.
393+
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;github&rsquo;, &lsquo;generic&rsquo;.
394394
When not specified, defaults to &lsquo;generic&rsquo;.</p>
395395
</td>
396396
</tr>
@@ -1730,7 +1730,7 @@ string
17301730
</td>
17311731
<td>
17321732
<em>(Optional)</em>
1733-
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;generic&rsquo;.
1733+
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;github&rsquo;, &lsquo;generic&rsquo;.
17341734
When not specified, defaults to &lsquo;generic&rsquo;.</p>
17351735
</td>
17361736
</tr>

docs/spec/v1/gitrepositories.md

+45
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ Supported options are:
221221

222222
- `generic`
223223
- `azure`
224+
- `github`
224225

225226
When provider is not specified, it defaults to `generic` indicating that
226227
mechanisms using `spec.secretRef` are used for authentication.
@@ -296,6 +297,50 @@ must follow this format:
296297
```
297298
https://dev.azure.com/{your-organization}/{your-project}/_git/{your-repository}
298299
```
300+
#### GitHub
301+
302+
The `github` provider can be used to authenticate to git repositories using
303+
[GitHub Apps](https://docs.github.com/en/apps/overview).
304+
305+
##### Pre-requisites
306+
307+
- [Register]((https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app))
308+
the GitHub App with the necessary permissions and [generate a private
309+
key](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps)
310+
for the app.
311+
312+
- [Install](https://docs.github.com/en/apps/using-github-apps/installing-your-own-github-app)
313+
the app in the organization/account.
314+
315+
##### Configure GitHub App secret
316+
317+
The GitHub App information is specified in `.spec.secretRef` in the format
318+
specified below:
319+
320+
* Get the App ID from the app settings page at
321+
`https://github.com/settings/apps/<app-name>`.
322+
* Get the App Installation ID from the app installations page at
323+
`https://github.com/settings/installations`. Click the installed app, the URL
324+
will contain the installation ID
325+
`https://github.com/settings/installations/<installation-id>`. For
326+
organizations, the first part of the URL may be different, but it follows the
327+
same pattern.
328+
* The private key that was generated in the pre-requisites.
329+
330+
```yaml
331+
apiVersion: v1
332+
kind: Secret
333+
metadata:
334+
name: github-sa
335+
type: Opaque
336+
stringData:
337+
githubAppID: <app-id>
338+
githubAppInstallationID: <app-installation-id>
339+
githubAppPrivateKey: |
340+
-----BEGIN RSA PRIVATE KEY-----
341+
...
342+
-----END RSA PRIVATE KEY-----
343+
```
299344

300345
### Interval
301346

go.mod

+8
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ require (
137137
github.com/beorn7/perks v1.0.1 // indirect
138138
github.com/blang/semver v3.5.1+incompatible // indirect
139139
github.com/blang/semver/v4 v4.0.0 // indirect
140+
github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 // indirect
140141
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
141142
github.com/buildkite/agent/v3 v3.76.2 // indirect
142143
github.com/buildkite/go-pipeline v0.10.0 // indirect
@@ -221,6 +222,7 @@ require (
221222
github.com/google/go-cmp v0.6.0 // indirect
222223
github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230516205744-dbecb1de8cfa // indirect
223224
github.com/google/go-github/v55 v55.0.0 // indirect
225+
github.com/google/go-github/v62 v62.0.0 // indirect
224226
github.com/google/go-querystring v1.1.0 // indirect
225227
github.com/google/gofuzz v1.2.0 // indirect
226228
github.com/google/s2a-go v0.1.8 // indirect
@@ -407,3 +409,9 @@ require (
407409
)
408410

409411
retract v0.32.0 // Refers to incorrect ./api version.
412+
413+
replace github.com/fluxcd/pkg/auth => github.com/dipti-pai/pkg/auth v0.0.0-20241021220529-d8b57af173e5
414+
415+
replace github.com/fluxcd/pkg/git => github.com/dipti-pai/pkg/git v0.0.0-20241021220529-d8b57af173e5
416+
417+
replace github.com/fluxcd/pkg/git/gogit => github.com/dipti-pai/pkg/git/gogit v0.0.0-20241021220529-d8b57af173e5

go.sum

+10-6
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
202202
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
203203
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
204204
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
205+
github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 h1:R9d0v+iobRHSaE4wKUnXFiZp53AL4ED5MzgEMwGTZag=
206+
github.com/bradleyfalzon/ghinstallation/v2 v2.11.0/go.mod h1:0LWKQwOHewXO/1acI6TtyE0Xc4ObDb2rFN7eHBAG71M=
205207
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
206208
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
207209
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
@@ -290,6 +292,12 @@ github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1G
290292
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y=
291293
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
292294
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
295+
github.com/dipti-pai/pkg/auth v0.0.0-20241021220529-d8b57af173e5 h1:IqrCZF29CugUuhO0F0ICDd7Ces9HNYyvuqd6Cq7JrkQ=
296+
github.com/dipti-pai/pkg/auth v0.0.0-20241021220529-d8b57af173e5/go.mod h1:sVEQPJ2wMYr6VgW7Xphy8gci1PsmWvJR1Y1MowePSRM=
297+
github.com/dipti-pai/pkg/git v0.0.0-20241021220529-d8b57af173e5 h1:FOgeSM47/CHJqPT0pff5l+ZOIkmkPDUqdwJM0Jn7OhQ=
298+
github.com/dipti-pai/pkg/git v0.0.0-20241021220529-d8b57af173e5/go.mod h1:QAX8mZHYlAmcMwPXsx4x3WC20J4FFQjeigp3CPZJQ8I=
299+
github.com/dipti-pai/pkg/git/gogit v0.0.0-20241021220529-d8b57af173e5 h1:LVGNqrPRBQN7vrVjblat2AMCz7xwT5dl8LUBthHB0VM=
300+
github.com/dipti-pai/pkg/git/gogit v0.0.0-20241021220529-d8b57af173e5/go.mod h1:1Jrqq3A9tAQUGYa4fA4Jc2t54z8rtdHB/5Ge0Q3bIQg=
293301
github.com/distribution/distribution/v3 v3.0.0-beta.1 h1:X+ELTxPuZ1Xe5MsD3kp2wfGUhc8I+MPfRis8dZ818Ic=
294302
github.com/distribution/distribution/v3 v3.0.0-beta.1/go.mod h1:O9O8uamhHzWWQVTjuQpyYUVm/ShPHPUDgvQMpHGVBDs=
295303
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
@@ -348,14 +356,8 @@ github.com/fluxcd/pkg/apis/event v0.10.1 h1:3PIAWOtEWblw7R2NUiEMFahRLs3cyYWXQpsM
348356
github.com/fluxcd/pkg/apis/event v0.10.1/go.mod h1:MuOoVHW27i0KOoEEerSOg49VdUy4etKc3thbQIeXAmg=
349357
github.com/fluxcd/pkg/apis/meta v1.6.1 h1:maLhcRJ3P/70ArLCY/LF/YovkxXbX+6sTWZwZQBeNq0=
350358
github.com/fluxcd/pkg/apis/meta v1.6.1/go.mod h1:YndB/gxgGZmKfqpAfFxyCDNFJFP0ikpeJzs66jwq280=
351-
github.com/fluxcd/pkg/auth v0.0.1 h1:3fMg1EdkQdY2Rv1qHbiPPWCBa27xsNeu09y9SuZk6Co=
352-
github.com/fluxcd/pkg/auth v0.0.1/go.mod h1:tdCkiB3/LBg7CcxX1fhVmM5ZjDIaOduK0XX88pBXie0=
353359
github.com/fluxcd/pkg/cache v0.0.4 h1:TM733caGoj58GFCOKQN3GajdSVmFx8yNx0HY6l5wV+M=
354360
github.com/fluxcd/pkg/cache v0.0.4/go.mod h1:jE7QdMvS9SZcdQaDhUYUm2/fV/KORA362iCNMLdH4pw=
355-
github.com/fluxcd/pkg/git v0.21.0 h1:5FfcKj9bDVz8KwoOQUOSJABLMeSdhvLBf7yctwwuMzc=
356-
github.com/fluxcd/pkg/git v0.21.0/go.mod h1:iCCmUCunoFLgntySJfIDxsHGYfS97ky990gEKIDZ9lo=
357-
github.com/fluxcd/pkg/git/gogit v0.21.0 h1:iR2kzW1XrcBDYuC8zVIAdC/2/aeXuRkZ9jupdd54E6I=
358-
github.com/fluxcd/pkg/git/gogit v0.21.0/go.mod h1:gyoSlEIqzsOiTwSL0iFuEiJat+W0uGgc+WEiCVC1xk8=
359361
github.com/fluxcd/pkg/gittestserver v0.13.1 h1:5rXF8ANlk6wtAsvqH7tI7gaO2zhMySftf7ALh0AhfU4=
360362
github.com/fluxcd/pkg/gittestserver v0.13.1/go.mod h1:nPO7ibtBRgLWFHTSvxI63zZubJXU82cVMH6nViVnHsY=
361363
github.com/fluxcd/pkg/helmtestserver v0.20.0 h1:eNeon7D92DYkTnBShGBS1l5blpjW7IQ21U0gTpArbiE=
@@ -526,6 +528,8 @@ github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-2023051620574
526528
github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230516205744-dbecb1de8cfa/go.mod h1:KdL98/Va8Dy1irB6lTxIRIQ7bQj4lbrlvqUzKEQ+ZBU=
527529
github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
528530
github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA=
531+
github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4=
532+
github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4=
529533
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
530534
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
531535
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=

internal/controller/gitrepository_controller.go

+19-7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
securejoin "github.com/cyphar/filepath-securejoin"
3030
"github.com/fluxcd/pkg/auth/azure"
31+
"github.com/fluxcd/pkg/auth/github"
3132
"github.com/fluxcd/pkg/runtime/logger"
3233
"github.com/go-git/go-git/v5/plumbing/transport"
3334
corev1 "k8s.io/api/core/v1"
@@ -613,10 +614,11 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
613614
// transport.ProxyOptions object using those settings and then returns it.
614615
func (r *GitRepositoryReconciler) getProxyOpts(ctx context.Context, proxySecretName,
615616
proxySecretNamespace string) (*transport.ProxyOptions, error) {
616-
proxyData, err := r.getSecretData(ctx, proxySecretName, proxySecretNamespace)
617+
proxySecret, err := r.getSecret(ctx, proxySecretName, proxySecretNamespace)
617618
if err != nil {
618619
return nil, fmt.Errorf("failed to get proxy secret '%s/%s': %w", proxySecretNamespace, proxySecretName, err)
619620
}
621+
proxyData := proxySecret.Data
620622
address, ok := proxyData["address"]
621623
if !ok {
622624
return nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", proxySecretNamespace, proxySecretName)
@@ -635,12 +637,14 @@ func (r *GitRepositoryReconciler) getProxyOpts(ctx context.Context, proxySecretN
635637
// URL and returns it.
636638
func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1.GitRepository, u url.URL) (*git.AuthOptions, error) {
637639
var authData map[string][]byte
640+
var authSecret corev1.Secret
638641
if obj.Spec.SecretRef != nil {
639642
var err error
640-
authData, err = r.getSecretData(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
643+
authSecret, err = r.getSecret(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
641644
if err != nil {
642645
return nil, fmt.Errorf("failed to get secret '%s/%s': %w", obj.GetNamespace(), obj.Spec.SecretRef.Name, err)
643646
}
647+
authData = authSecret.Data
644648
}
645649

646650
// Configure authentication strategy to access the source
@@ -650,28 +654,36 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
650654
}
651655

652656
// Configure provider authentication if specified in spec
653-
if obj.GetProvider() == sourcev1.GitProviderAzure {
657+
switch obj.GetProvider() {
658+
case sourcev1.GitProviderAzure:
654659
authOpts.ProviderOpts = &git.ProviderOptions{
655-
Name: obj.GetProvider(),
660+
Name: sourcev1.GitProviderAzure,
656661
AzureOpts: []azure.OptFunc{
657662
azure.WithAzureDevOpsScope(),
658663
},
659664
}
665+
case sourcev1.GitProviderGitHub:
666+
authOpts.ProviderOpts = &git.ProviderOptions{
667+
Name: sourcev1.GitProviderGitHub,
668+
GitHubOpts: []github.OptFunc{
669+
github.WithSecret(authSecret),
670+
},
671+
}
660672
}
661673

662674
return authOpts, nil
663675
}
664676

665-
func (r *GitRepositoryReconciler) getSecretData(ctx context.Context, name, namespace string) (map[string][]byte, error) {
677+
func (r *GitRepositoryReconciler) getSecret(ctx context.Context, name, namespace string) (corev1.Secret, error) {
666678
key := types.NamespacedName{
667679
Namespace: namespace,
668680
Name: name,
669681
}
670682
var secret corev1.Secret
671683
if err := r.Client.Get(ctx, key, &secret); err != nil {
672-
return nil, err
684+
return secret, err
673685
}
674-
return secret.Data, nil
686+
return secret, nil
675687
}
676688

677689
// reconcileArtifact archives a new Artifact to the Storage, if the current

internal/controller/gitrepository_controller_test.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -686,23 +686,35 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
686686
func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
687687
tests := []struct {
688688
name string
689+
url string
689690
beforeFunc func(obj *sourcev1.GitRepository)
690691
wantProviderOptsName string
691692
}{
692693
{
693694
name: "azure provider",
695+
url: "https://dev.azure.com/foo/bar/_git/baz",
694696
beforeFunc: func(obj *sourcev1.GitRepository) {
695697
obj.Spec.Provider = sourcev1.GitProviderAzure
696698
},
697699
wantProviderOptsName: sourcev1.GitProviderAzure,
698700
},
701+
{
702+
name: "github provider",
703+
url: "https://github.com/org/repo.git",
704+
beforeFunc: func(obj *sourcev1.GitRepository) {
705+
obj.Spec.Provider = sourcev1.GitProviderGitHub
706+
},
707+
wantProviderOptsName: sourcev1.GitProviderGitHub,
708+
},
699709
{
700710
name: "generic provider",
711+
url: "https://example.com/org/repo",
701712
beforeFunc: func(obj *sourcev1.GitRepository) {
702713
obj.Spec.Provider = sourcev1.GitProviderGeneric
703714
},
704715
},
705716
{
717+
url: "https://example.com/org/repo",
706718
name: "no provider",
707719
},
708720
}
@@ -712,7 +724,8 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) {
712724
g := NewWithT(t)
713725
obj := &sourcev1.GitRepository{}
714726
r := &GitRepositoryReconciler{}
715-
url, _ := url.Parse("https://dev.azure.com/foo/bar/_git/baz")
727+
url, err := url.Parse(tt.url)
728+
g.Expect(err).ToNot(HaveOccurred())
716729

717730
if tt.beforeFunc != nil {
718731
tt.beforeFunc(obj)

0 commit comments

Comments
 (0)