Skip to content

Commit cd5eebf

Browse files
authored
Merge pull request #1860 from abhijith-darshan/feat/gh_app_tls
Add support for mTLS to GitHub App transport
2 parents bd6d090 + 46522f9 commit cd5eebf

File tree

5 files changed

+193
-141
lines changed

5 files changed

+193
-141
lines changed

docs/spec/v1/gitrepositories.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,11 @@ same pattern.
357357
- The private key that was generated in the pre-requisites.
358358
- (Optional) GitHub Enterprise Server users can set the base URL to
359359
`http(s)://HOSTNAME/api/v3`.
360+
- (Optional) If GitHub Enterprise Server uses a private CA, include its bundle (root and any intermediates) in `ca.crt`.
361+
If the `ca.crt` is specified, then it will be used for TLS verification for all API / Git over `HTTPS` requests to the GitHub Enterprise Server.
362+
363+
**NOTE:** If the secret contains `tls.crt`, `tls.key` then [mutual TLS configuration](#https-mutual-tls-authentication) will be automatically enabled.
364+
Omit these keys if the GitHub server does not support mutual TLS.
360365

361366
```yaml
362367
apiVersion: v1
@@ -372,6 +377,10 @@ stringData:
372377
...
373378
-----END RSA PRIVATE KEY-----
374379
githubAppBaseURL: "<github-enterprise-api-url>" #optional, required only for GitHub Enterprise Server users
380+
ca.crt: | #optional, for GitHub Enterprise Server users
381+
-----BEGIN CERTIFICATE-----
382+
...
383+
-----END CERTIFICATE-----
375384
```
376385

377386
Alternatively, the Flux CLI can be used to automatically create the secret with

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ require (
3030
github.com/fluxcd/pkg/apis/meta v1.18.0
3131
github.com/fluxcd/pkg/auth v0.21.0
3232
github.com/fluxcd/pkg/cache v0.10.0
33-
github.com/fluxcd/pkg/git v0.34.0
34-
github.com/fluxcd/pkg/git/gogit v0.37.0
33+
github.com/fluxcd/pkg/git v0.35.0
34+
github.com/fluxcd/pkg/git/gogit v0.38.0
3535
github.com/fluxcd/pkg/gittestserver v0.18.0
3636
github.com/fluxcd/pkg/helmtestserver v0.26.0
3737
github.com/fluxcd/pkg/http/transport v0.6.0
3838
github.com/fluxcd/pkg/lockedfile v0.6.0
3939
github.com/fluxcd/pkg/masktoken v0.7.0
4040
github.com/fluxcd/pkg/oci v0.52.0
41-
github.com/fluxcd/pkg/runtime v0.78.0
41+
github.com/fluxcd/pkg/runtime v0.79.0
4242
github.com/fluxcd/pkg/sourceignore v0.13.0
4343
github.com/fluxcd/pkg/ssh v0.20.0
4444
github.com/fluxcd/pkg/tar v0.13.0

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -382,10 +382,10 @@ github.com/fluxcd/pkg/auth v0.21.0 h1:ckAQqP12wuptXEkMY18SQKWEY09m9e6yI0mEMsDV15
382382
github.com/fluxcd/pkg/auth v0.21.0/go.mod h1:MXmpsXT97c874HCw5hnfqFUP7TsG8/Ss1vFrk8JccfM=
383383
github.com/fluxcd/pkg/cache v0.10.0 h1:M+OGDM4da1cnz7q+sZSBtkBJHpiJsLnKVmR9OdMWxEY=
384384
github.com/fluxcd/pkg/cache v0.10.0/go.mod h1:pPXRzQUDQagsCniuOolqVhnAkbNgYOg8d2cTliPs7ME=
385-
github.com/fluxcd/pkg/git v0.34.0 h1:qTViWkfpEDnjzySyKRKliqUeGj/DznqlkmPhaDNIsFY=
386-
github.com/fluxcd/pkg/git v0.34.0/go.mod h1:F9Asm3MlLW4uZx3FF92+bqho+oktdMdnTn/QmXe56NE=
387-
github.com/fluxcd/pkg/git/gogit v0.37.0 h1:JINylFYpwrxS3MCu5Ei+g6XPgxbs5lv9PppIYYr07KY=
388-
github.com/fluxcd/pkg/git/gogit v0.37.0/go.mod h1:X7YzW5mb4srA05h4SpL2OEGEHq02tbXQF5DPJen9hlc=
385+
github.com/fluxcd/pkg/git v0.35.0 h1:mAauhsdfxNW4yQdXviVlvcN/uCGGG0+6p5D1+HFZI9w=
386+
github.com/fluxcd/pkg/git v0.35.0/go.mod h1:F9Asm3MlLW4uZx3FF92+bqho+oktdMdnTn/QmXe56NE=
387+
github.com/fluxcd/pkg/git/gogit v0.38.0 h1:222KmjpKf9pxqi8rAtm1omDcpGTY4JkahLrAwZ3AcwU=
388+
github.com/fluxcd/pkg/git/gogit v0.38.0/go.mod h1:kHStdfd/AtkH5ED0UEWP2tmMGnfxg1GG92D29M+lRJ0=
389389
github.com/fluxcd/pkg/gittestserver v0.18.0 h1:jkuLmzWFfq+v1ziI0LspZrUzc5WzCO98BaWb8OVRPtk=
390390
github.com/fluxcd/pkg/gittestserver v0.18.0/go.mod h1:2wDLqUkPuixk/8pGQdef9ewaGJXf7Z+xHDVq8PIFG4E=
391391
github.com/fluxcd/pkg/helmtestserver v0.26.0 h1:gKw1MGqWwN94nzs2yg3WKgMxi1RqqlDZXlGziaNCcv4=
@@ -398,8 +398,8 @@ github.com/fluxcd/pkg/masktoken v0.7.0 h1:pitmyOg2pUVdW+nn2Lk/xqm2TaA08uxvOC0ns3
398398
github.com/fluxcd/pkg/masktoken v0.7.0/go.mod h1:Lc1uoDjO1GY6+YdkK+ZqqBIBWquyV58nlSJ5S1N1IYU=
399399
github.com/fluxcd/pkg/oci v0.52.0 h1:rkHMtXYm21MtDrjNcR5KScqOe6C1JHPExoShuVdNm8M=
400400
github.com/fluxcd/pkg/oci v0.52.0/go.mod h1:5J6IhHoDVYCVeBEC+4E3nPeKh7d0kjJ8IEL6NVCiTx4=
401-
github.com/fluxcd/pkg/runtime v0.78.0 h1:xwNZqnazmgURGuLiHDbzST6BI5K9fvZuNS4eMVY35Es=
402-
github.com/fluxcd/pkg/runtime v0.78.0/go.mod h1:iGhdaEq+lMJQTJNAFEPOU4gUJ7kt3yeDcJPZy7O9IUw=
401+
github.com/fluxcd/pkg/runtime v0.79.0 h1:9tv79EiQDx/QJH9mYDd9kZ9WybCVWBUGoiBHij+eKkc=
402+
github.com/fluxcd/pkg/runtime v0.79.0/go.mod h1:iGhdaEq+lMJQTJNAFEPOU4gUJ7kt3yeDcJPZy7O9IUw=
403403
github.com/fluxcd/pkg/sourceignore v0.13.0 h1:ZvkzX2WsmyZK9cjlqOFFW1onHVzhPZIqDbCh96rPqbU=
404404
github.com/fluxcd/pkg/sourceignore v0.13.0/go.mod h1:Z9H1GoBx0ljOhptnzoV0PL6Nd/UzwKcSphP27lqb4xI=
405405
github.com/fluxcd/pkg/ssh v0.20.0 h1:Ak0laIYIc/L8lEfqls/LDWRW8wYPESGaravQsCRGLb8=

internal/controller/gitrepository_controller.go

Lines changed: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
authutils "github.com/fluxcd/pkg/auth/utils"
3232
"github.com/fluxcd/pkg/git/github"
3333
"github.com/fluxcd/pkg/runtime/logger"
34+
"github.com/fluxcd/pkg/runtime/secrets"
3435
"github.com/go-git/go-git/v5/plumbing/transport"
3536
corev1 "k8s.io/api/core/v1"
3637
"k8s.io/apimachinery/pkg/runtime"
@@ -486,7 +487,11 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
486487
var proxyURL *url.URL
487488
if obj.Spec.ProxySecretRef != nil {
488489
var err error
489-
proxyOpts, proxyURL, err = r.getProxyOpts(ctx, obj.Spec.ProxySecretRef.Name, obj.GetNamespace())
490+
secretRef := types.NamespacedName{
491+
Name: obj.Spec.ProxySecretRef.Name,
492+
Namespace: obj.GetNamespace(),
493+
}
494+
proxyURL, err = secrets.ProxyURLFromSecretRef(ctx, r.Client, secretRef)
490495
if err != nil {
491496
e := serror.NewGeneric(
492497
fmt.Errorf("failed to configure proxy options: %w", err),
@@ -496,6 +501,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
496501
// Return error as the world as observed may change
497502
return sreconcile.ResultEmpty, e
498503
}
504+
proxyOpts = &transport.ProxyOptions{URL: proxyURL.String()}
499505
}
500506

501507
u, err := url.Parse(obj.Spec.URL)
@@ -618,52 +624,16 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
618624
return sreconcile.ResultSuccess, nil
619625
}
620626

621-
// getProxyOpts fetches the secret containing the proxy settings, constructs a
622-
// transport.ProxyOptions object using those settings and then returns it.
623-
func (r *GitRepositoryReconciler) getProxyOpts(ctx context.Context, proxySecretName,
624-
proxySecretNamespace string) (*transport.ProxyOptions, *url.URL, error) {
625-
proxyData, err := r.getSecretData(ctx, proxySecretName, proxySecretNamespace)
626-
if err != nil {
627-
return nil, nil, fmt.Errorf("failed to get proxy secret '%s/%s': %w", proxySecretNamespace, proxySecretName, err)
628-
}
629-
b, ok := proxyData["address"]
630-
if !ok {
631-
return nil, nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", proxySecretNamespace, proxySecretName)
632-
}
633-
634-
address := string(b)
635-
username := string(proxyData["username"])
636-
password := string(proxyData["password"])
637-
638-
proxyOpts := &transport.ProxyOptions{
639-
URL: address,
640-
Username: username,
641-
Password: password,
642-
}
643-
644-
proxyURL, err := url.Parse(string(address))
645-
if err != nil {
646-
return nil, nil, fmt.Errorf("invalid address in proxy secret '%s/%s': %w", proxySecretNamespace, proxySecretName, err)
647-
}
648-
switch {
649-
case username != "" && password == "":
650-
proxyURL.User = url.User(username)
651-
case username != "" && password != "":
652-
proxyURL.User = url.UserPassword(username, password)
653-
}
654-
655-
return proxyOpts, proxyURL, nil
656-
}
657-
658627
// getAuthOpts fetches the secret containing the auth options (if specified),
659628
// constructs a git.AuthOptions object using those options along with the provided
660629
// URL and returns it.
661630
func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1.GitRepository,
662631
u url.URL, proxyURL *url.URL) (*git.AuthOptions, error) {
632+
var secret *corev1.Secret
663633
var authData map[string][]byte
664634
if obj.Spec.SecretRef != nil {
665635
var err error
666-
authData, err = r.getSecretData(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
636+
secret, err = r.getSecret(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
667637
if err != nil {
668638
e := serror.NewGeneric(
669639
fmt.Errorf("failed to get secret '%s/%s': %w", obj.GetNamespace(), obj.Spec.SecretRef.Name, err),
@@ -672,6 +642,7 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
672642
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
673643
return nil, e
674644
}
645+
authData = secret.Data
675646
}
676647

677648
// Configure authentication strategy to access the source
@@ -718,24 +689,38 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
718689
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
719690
return nil, e
720691
}
721-
692+
targetURL := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
693+
authMethods, err := secrets.AuthMethodsFromSecret(ctx, secret, secrets.WithTargetURL(targetURL), secrets.WithTLSSystemCertPool())
694+
if err != nil {
695+
return nil, err
696+
}
697+
if !authMethods.HasGitHubAppData() {
698+
e := serror.NewGeneric(
699+
fmt.Errorf("secretRef with github app data must be specified when provider is set to github"),
700+
sourcev1.InvalidProviderConfigurationReason,
701+
)
702+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
703+
return nil, e
704+
}
722705
getCreds = func() (*authutils.GitCredentials, error) {
723-
var opts []github.OptFunc
706+
var appOpts []github.OptFunc
724707

725-
if len(authData) > 0 {
726-
opts = append(opts, github.WithAppData(authData))
727-
}
708+
appOpts = append(appOpts, github.WithAppData(authMethods.GitHubAppData))
728709

729710
if proxyURL != nil {
730-
opts = append(opts, github.WithProxyURL(proxyURL))
711+
appOpts = append(appOpts, github.WithProxyURL(proxyURL))
731712
}
732713

733714
if r.TokenCache != nil {
734-
opts = append(opts, github.WithCache(r.TokenCache, sourcev1.GitRepositoryKind,
715+
appOpts = append(appOpts, github.WithCache(r.TokenCache, sourcev1.GitRepositoryKind,
735716
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile))
736717
}
737718

738-
username, password, err := github.GetCredentials(ctx, opts...)
719+
if authMethods.HasTLS() {
720+
appOpts = append(appOpts, github.WithTLSConfig(authMethods.TLS))
721+
}
722+
723+
username, password, err := github.GetCredentials(ctx, appOpts...)
739724
if err != nil {
740725
return nil, err
741726
}
@@ -772,16 +757,16 @@ func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1
772757
return opts, nil
773758
}
774759

775-
func (r *GitRepositoryReconciler) getSecretData(ctx context.Context, name, namespace string) (map[string][]byte, error) {
760+
func (r *GitRepositoryReconciler) getSecret(ctx context.Context, name, namespace string) (*corev1.Secret, error) {
776761
key := types.NamespacedName{
777762
Namespace: namespace,
778763
Name: name,
779764
}
780-
var secret corev1.Secret
781-
if err := r.Client.Get(ctx, key, &secret); err != nil {
782-
return nil, err
765+
secret := &corev1.Secret{}
766+
if err := r.Client.Get(ctx, key, secret); err != nil {
767+
return nil, fmt.Errorf("failed to get secret '%s/%s': %w", namespace, name, err)
783768
}
784-
return secret.Data, nil
769+
return secret, nil
785770
}
786771

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

0 commit comments

Comments
 (0)