Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC-007] Implement GitHub app authentication for git repositories. #1647

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/v1/condition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,8 @@ const (

// InvalidSTSConfigurationReason signals that the STS configurtion is invalid.
InvalidSTSConfigurationReason string = "InvalidSTSConfiguration"

// InvalidProviderConfigurationReason signals that the provider
// configuration is invalid.
InvalidProviderConfigurationReason string = "InvalidProviderConfiguration"
)
8 changes: 6 additions & 2 deletions api/v1/gitrepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const (
// GitProviderAzure provides support for authentication to azure
// repositories using Managed Identity.
GitProviderAzure string = "azure"

// GitProviderGitHub provides support for authentication to git
// repositories using GitHub App authentication
GitProviderGitHub string = "github"
)

const (
Expand Down Expand Up @@ -88,9 +92,9 @@ type GitRepositorySpec struct {
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`

// Provider used for authentication, can be 'azure', 'generic'.
// Provider used for authentication, can be 'azure', 'github', 'generic'.
// When not specified, defaults to 'generic'.
// +kubebuilder:validation:Enum=generic;azure
// +kubebuilder:validation:Enum=generic;azure;github
// +optional
Provider string `json:"provider,omitempty"`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ spec:
type: string
provider:
description: |-
Provider used for authentication, can be 'azure', 'generic'.
Provider used for authentication, can be 'azure', 'github', 'generic'.
When not specified, defaults to 'generic'.
enum:
- generic
- azure
- github
type: string
proxySecretRef:
description: |-
Expand Down
4 changes: 2 additions & 2 deletions docs/api/v1/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ string
</td>
<td>
<em>(Optional)</em>
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;generic&rsquo;.
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;github&rsquo;, &lsquo;generic&rsquo;.
When not specified, defaults to &lsquo;generic&rsquo;.</p>
</td>
</tr>
Expand Down Expand Up @@ -1730,7 +1730,7 @@ string
</td>
<td>
<em>(Optional)</em>
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;generic&rsquo;.
<p>Provider used for authentication, can be &lsquo;azure&rsquo;, &lsquo;github&rsquo;, &lsquo;generic&rsquo;.
When not specified, defaults to &lsquo;generic&rsquo;.</p>
</td>
</tr>
Expand Down
59 changes: 59 additions & 0 deletions docs/spec/v1/gitrepositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Supported options are:

- `generic`
- `azure`
- `github`

When provider is not specified, it defaults to `generic` indicating that
mechanisms using `spec.secretRef` are used for authentication.
Expand Down Expand Up @@ -296,6 +297,64 @@ must follow this format:
```
https://dev.azure.com/{your-organization}/{your-project}/_git/{your-repository}
```
#### GitHub

The `github` provider can be used to authenticate to Git repositories using
[GitHub Apps](https://docs.github.com/en/apps/overview).

##### Pre-requisites

- [Register](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app)
the GitHub App with the necessary permissions and [generate a private
key](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps)
for the app.

- [Install](https://docs.github.com/en/apps/using-github-apps/installing-your-own-github-app)
the app in the organization/account configuring access to the necessary
repositories.

##### Configure GitHub App secret

The GitHub App information is specified in `.spec.secretRef` in the format
specified below:

- Get the App ID from the app settings page at
`https://github.com/settings/apps/<app-name>`.
- Get the App Installation ID from the app installations page at
`https://github.com/settings/installations`. Click the installed app, the URL
will contain the installation ID
`https://github.com/settings/installations/<installation-id>`. For
organizations, the first part of the URL may be different, but it follows the
same pattern.
- The private key that was generated in the pre-requisites.
- (Optional) GitHub Enterprise Server users can set the base URL to
`http(s)://HOSTNAME/api/v3`.

```yaml
apiVersion: v1
kind: Secret
dipti-pai marked this conversation as resolved.
Show resolved Hide resolved
metadata:
name: github-sa
type: Opaque
stringData:
githubAppID: "<app-id>"
githubAppInstallationID: "<app-installation-id>"
githubAppPrivateKey: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
githubAppBaseURL: "<github-enterprise-api-url>" #optional, required only for GitHub Enterprise Server users
```

Alternatively, the Flux CLI can be used to automatically create the secret with
the github app authentication information.

```sh
flux create secret githubapp ghapp-secret \
--app-id=1 \
--app-installation-id=3 \
--app-private-key=~/private-key.pem
```

### Interval

Expand Down
55 changes: 44 additions & 11 deletions internal/controller/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

securejoin "github.com/cyphar/filepath-securejoin"
"github.com/fluxcd/pkg/auth/azure"
"github.com/fluxcd/pkg/auth/github"
"github.com/fluxcd/pkg/runtime/logger"
"github.com/go-git/go-git/v5/plumbing/transport"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -504,13 +505,8 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch

authOpts, err := r.getAuthOpts(ctx, obj, *u)
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to configure authentication options: %w", err),
sourcev1.AuthenticationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
// Return error as the world as observed may change
return sreconcile.ResultEmpty, e
return sreconcile.ResultEmpty, err
}

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

// Configure authentication strategy to access the source
authOpts, err := git.NewAuthOptions(u, authData)
if err != nil {
return nil, err
e := serror.NewGeneric(
fmt.Errorf("failed to configure authentication options: %w", err),
sourcev1.AuthenticationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
return nil, e
}

// Configure provider authentication if specified in spec
if obj.GetProvider() == sourcev1.GitProviderAzure {
switch obj.GetProvider() {
case sourcev1.GitProviderAzure:
authOpts.ProviderOpts = &git.ProviderOptions{
Name: obj.GetProvider(),
Name: sourcev1.GitProviderAzure,
AzureOpts: []azure.OptFunc{
azure.WithAzureDevOpsScope(),
},
}
}
case sourcev1.GitProviderGitHub:
darkowlzz marked this conversation as resolved.
Show resolved Hide resolved
// if provider is github, but secret ref is not specified
if obj.Spec.SecretRef == nil {
e := serror.NewStalling(
fmt.Errorf("secretRef with github app data must be specified when provider is set to github"),
sourcev1.InvalidProviderConfigurationReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
return nil, e
}

authOpts.ProviderOpts = &git.ProviderOptions{
Name: sourcev1.GitProviderGitHub,
GitHubOpts: []github.OptFunc{
github.WithAppData(authData),
},
}
default:
// analyze secret, if it has github app data, perhaps provider should have been github.
if appID := authData[github.AppIDKey]; len(appID) != 0 {
e := serror.NewStalling(
fmt.Errorf("secretRef '%s/%s' has github app data but provider is not set to github", obj.GetNamespace(), obj.Spec.SecretRef.Name),
sourcev1.InvalidProviderConfigurationReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
return nil, e
}
}
return authOpts, nil
}

Expand Down
Loading
Loading