Skip to content

Commit fe5af75

Browse files
authored
Merge pull request #1647 from dipti-pai/github-app-auth
[RFC-007] Implement GitHub app authentication for git repositories.
2 parents fe7b1fe + 1ed8459 commit fe5af75

File tree

7 files changed

+255
-24
lines changed

7 files changed

+255
-24
lines changed

api/v1/condition_types.go

+4
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,8 @@ const (
111111

112112
// InvalidSTSConfigurationReason signals that the STS configurtion is invalid.
113113
InvalidSTSConfigurationReason string = "InvalidSTSConfiguration"
114+
115+
// InvalidProviderConfigurationReason signals that the provider
116+
// configuration is invalid.
117+
InvalidProviderConfigurationReason string = "InvalidProviderConfiguration"
114118
)

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

+59
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,64 @@ 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 configuring access to the necessary
314+
repositories.
315+
316+
##### Configure GitHub App secret
317+
318+
The GitHub App information is specified in `.spec.secretRef` in the format
319+
specified below:
320+
321+
- Get the App ID from the app settings page at
322+
`https://github.com/settings/apps/<app-name>`.
323+
- Get the App Installation ID from the app installations page at
324+
`https://github.com/settings/installations`. Click the installed app, the URL
325+
will contain the installation ID
326+
`https://github.com/settings/installations/<installation-id>`. For
327+
organizations, the first part of the URL may be different, but it follows the
328+
same pattern.
329+
- The private key that was generated in the pre-requisites.
330+
- (Optional) GitHub Enterprise Server users can set the base URL to
331+
`http(s)://HOSTNAME/api/v3`.
332+
333+
```yaml
334+
apiVersion: v1
335+
kind: Secret
336+
metadata:
337+
name: github-sa
338+
type: Opaque
339+
stringData:
340+
githubAppID: "<app-id>"
341+
githubAppInstallationID: "<app-installation-id>"
342+
githubAppPrivateKey: |
343+
-----BEGIN RSA PRIVATE KEY-----
344+
...
345+
-----END RSA PRIVATE KEY-----
346+
githubAppBaseURL: "<github-enterprise-api-url>" #optional, required only for GitHub Enterprise Server users
347+
```
348+
349+
Alternatively, the Flux CLI can be used to automatically create the secret with
350+
the github app authentication information.
351+
352+
```sh
353+
flux create secret githubapp ghapp-secret \
354+
--app-id=1 \
355+
--app-installation-id=3 \
356+
--app-private-key=~/private-key.pem
357+
```
299358

300359
### Interval
301360

internal/controller/gitrepository_controller.go

+44-11
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"
@@ -504,13 +505,8 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
504505

505506
authOpts, err := r.getAuthOpts(ctx, obj, *u)
506507
if err != nil {
507-
e := serror.NewGeneric(
508-
fmt.Errorf("failed to configure authentication options: %w", err),
509-
sourcev1.AuthenticationFailedReason,
510-
)
511-
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
512508
// Return error as the world as observed may change
513-
return sreconcile.ResultEmpty, e
509+
return sreconcile.ResultEmpty, err
514510
}
515511

516512
// Fetch the included artifact metadata.
@@ -637,26 +633,63 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
637633
var err error
638634
authData, err = r.getSecretData(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
639635
if err != nil {
640-
return nil, fmt.Errorf("failed to get secret '%s/%s': %w", obj.GetNamespace(), obj.Spec.SecretRef.Name, err)
636+
e := serror.NewGeneric(
637+
fmt.Errorf("failed to get secret '%s/%s': %w", obj.GetNamespace(), obj.Spec.SecretRef.Name, err),
638+
sourcev1.AuthenticationFailedReason,
639+
)
640+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
641+
return nil, e
641642
}
642643
}
643644

644645
// Configure authentication strategy to access the source
645646
authOpts, err := git.NewAuthOptions(u, authData)
646647
if err != nil {
647-
return nil, err
648+
e := serror.NewGeneric(
649+
fmt.Errorf("failed to configure authentication options: %w", err),
650+
sourcev1.AuthenticationFailedReason,
651+
)
652+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
653+
return nil, e
648654
}
649655

650656
// Configure provider authentication if specified in spec
651-
if obj.GetProvider() == sourcev1.GitProviderAzure {
657+
switch obj.GetProvider() {
658+
case sourcev1.GitProviderAzure:
652659
authOpts.ProviderOpts = &git.ProviderOptions{
653-
Name: obj.GetProvider(),
660+
Name: sourcev1.GitProviderAzure,
654661
AzureOpts: []azure.OptFunc{
655662
azure.WithAzureDevOpsScope(),
656663
},
657664
}
658-
}
665+
case sourcev1.GitProviderGitHub:
666+
// if provider is github, but secret ref is not specified
667+
if obj.Spec.SecretRef == nil {
668+
e := serror.NewStalling(
669+
fmt.Errorf("secretRef with github app data must be specified when provider is set to github"),
670+
sourcev1.InvalidProviderConfigurationReason,
671+
)
672+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
673+
return nil, e
674+
}
659675

676+
authOpts.ProviderOpts = &git.ProviderOptions{
677+
Name: sourcev1.GitProviderGitHub,
678+
GitHubOpts: []github.OptFunc{
679+
github.WithAppData(authData),
680+
},
681+
}
682+
default:
683+
// analyze secret, if it has github app data, perhaps provider should have been github.
684+
if appID := authData[github.AppIDKey]; len(appID) != 0 {
685+
e := serror.NewStalling(
686+
fmt.Errorf("secretRef '%s/%s' has github app data but provider is not set to github", obj.GetNamespace(), obj.Spec.SecretRef.Name),
687+
sourcev1.InvalidProviderConfigurationReason,
688+
)
689+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
690+
return nil, e
691+
}
692+
}
660693
return authOpts, nil
661694
}
662695

0 commit comments

Comments
 (0)