Skip to content

Commit 6515aaf

Browse files
committed
gitrepo: Add support for specifying proxy per GitRepository
Add `.spec.proxy.secretRef.name` to `GitRepository` to allow referencing a secret containing the proxy settings to be used for all remote Git operations for a particular object. Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent 3b7798d commit 6515aaf

File tree

9 files changed

+229
-17
lines changed

9 files changed

+229
-17
lines changed

api/v1/gitrepository_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ type GitRepositorySpec struct {
7878
// +optional
7979
Verification *GitRepositoryVerification `json:"verify,omitempty"`
8080

81+
// ProxySecretRef specifies the Secret containing the proxy configuration
82+
// to use while communicating with the Git server.
83+
ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"`
84+
8185
// Ignore overrides the set of excluded patterns in the .sourceignore format
8286
// (which is the same as .gitignore). If not provided, a default will be used,
8387
// consult the documentation for your version to find out what those are.

api/v1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ spec:
9090
description: Interval at which to check the GitRepository for updates.
9191
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
9292
type: string
93+
proxySecretRef:
94+
description: ProxySecretRef specifies the Secret containing the proxy
95+
configuration to use while communicating with the Git server.
96+
properties:
97+
name:
98+
description: Name of the referent.
99+
type: string
100+
required:
101+
- name
102+
type: object
93103
recurseSubmodules:
94104
description: RecurseSubmodules enables the initialization of all submodules
95105
within the GitRepository as cloned from the URL, using their default

docs/api/v1/source.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,20 @@ signature(s).</p>
157157
</tr>
158158
<tr>
159159
<td>
160+
<code>proxySecretRef</code><br>
161+
<em>
162+
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
163+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
164+
</a>
165+
</em>
166+
</td>
167+
<td>
168+
<p>ProxySecretRef specifies the Secret containing the proxy configuration
169+
to use while communicating with the Git server.</p>
170+
</td>
171+
</tr>
172+
<tr>
173+
<td>
160174
<code>ignore</code><br>
161175
<em>
162176
string
@@ -593,6 +607,20 @@ signature(s).</p>
593607
</tr>
594608
<tr>
595609
<td>
610+
<code>proxySecretRef</code><br>
611+
<em>
612+
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
613+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
614+
</a>
615+
</em>
616+
</td>
617+
<td>
618+
<p>ProxySecretRef specifies the Secret containing the proxy configuration
619+
to use while communicating with the Git server.</p>
620+
</td>
621+
</tr>
622+
<tr>
623+
<td>
596624
<code>ignore</code><br>
597625
<em>
598626
string

docs/spec/v1/gitrepositories.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,53 @@ GitRepository, and changes to the resource or in the Git repository will not
433433
result in a new Artifact. When the field is set to `false` or removed, it will
434434
resume.
435435

436-
#### Proxy support
436+
### Proxy
437437

438-
When a proxy is configured in the source-controller Pod through the appropriate
439-
environment variables, for example `HTTPS_PROXY`, `NO_PROXY`, etc.
438+
`.spec.proxy.secretRef.name` is an optional field to specify a name of a Secret
439+
containing the proxy settings that need to be used for all remote Git operations
440+
for the particular GitRepository object. The Secret can contain three keys:
441+
442+
- `address`: The address of the proxy server. This is a required key.
443+
- `username`: The username to use if the proxy server is protected by basic
444+
authentication. This is an optinal key.
445+
- `password`: The password to use if the proxy server is protected by basic
446+
authentication. This is an optinal key.
447+
448+
The proxy server must be either HTTP/S or SOCKS5. You can use a SOCKS5 proxy
449+
with a HTTP/S Git repository url.
450+
451+
Examples:
452+
453+
```yaml
454+
---
455+
apiVersion: v1
456+
kind: Secret
457+
metadata:
458+
name: http-proxy
459+
type: Opaque
460+
stringData:
461+
address: http://proxy.com
462+
username: mandalorian
463+
password: grogu
464+
```
465+
466+
```yaml
467+
---
468+
apiVersion: v1
469+
kind: Secret
470+
metadata:
471+
name: ssh-proxy
472+
type: Opaque
473+
stringData:
474+
address: socks5://proxy.com
475+
username: mandalorian
476+
password: grogu
477+
```
478+
479+
Proxying can also be configured in the source-controller pod directly by using
480+
the standard environment variables such as `HTTPS_PROXY`, `ALL_PROXY`, etc.
481+
482+
`.spec.proxy.secretRef.name` takes precedence over all environment variables.
440483

441484
### Recurse submodules
442485

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ replace github.com/docker/docker => github.com/docker/docker v23.0.6+incompatibl
2121
// is compatible with github.com/google/go-containerregistry v0.15.x.
2222
replace github.com/google/go-containerregistry => github.com/google/go-containerregistry v0.14.1-0.20230409045903-ed5c185df419
2323

24+
// temp replace directive to enable the use of `WithProxy()`
25+
replace github.com/fluxcd/pkg/git/gogit => github.com/fluxcd/pkg/git/gogit v0.11.2-0.20230530083208-12339fbae0ae
26+
2427
require (
2528
cloud.google.com/go/storage v1.30.1
2629
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,8 @@ github.com/fluxcd/pkg/apis/meta v1.1.1 h1:sLAKLbEu7rRzJ+Mytffu3NcpfdbOBTa6hcpOQz
391391
github.com/fluxcd/pkg/apis/meta v1.1.1/go.mod h1:soCfzjFWbm1mqybDcOywWKTCEYlH3skpoNGTboVk234=
392392
github.com/fluxcd/pkg/git v0.12.3 h1:1KmRYTdcBKDUutg6NIT4x0BCCMT72PjjXs3AnHjybHY=
393393
github.com/fluxcd/pkg/git v0.12.3/go.mod h1:ID2sry5OqYbgJxvANc7s6V/YwafnQd7e1AGoDvwztAU=
394-
github.com/fluxcd/pkg/git/gogit v0.12.0 h1:0mCwQND0WpCVZYHLWcXJxRvKVcyWxH4JjMQFMaea8Q4=
395-
github.com/fluxcd/pkg/git/gogit v0.12.0/go.mod h1:Kn+GfYfZBBIaXmQj39cQvrDxT/6y8leQxXZ5/B+YYTQ=
394+
github.com/fluxcd/pkg/git/gogit v0.11.2-0.20230530083208-12339fbae0ae h1:JCU+9lTKHPIE7bS5Q9DUoGNa2QuGNhIch/3rt5v8Pso=
395+
github.com/fluxcd/pkg/git/gogit v0.11.2-0.20230530083208-12339fbae0ae/go.mod h1:Kn+GfYfZBBIaXmQj39cQvrDxT/6y8leQxXZ5/B+YYTQ=
396396
github.com/fluxcd/pkg/gittestserver v0.8.4 h1:rA/QUZnfH77ZZG+5xfMqjgEHJdzeeE6Nn1o8cops/bU=
397397
github.com/fluxcd/pkg/gittestserver v0.8.4/go.mod h1:i3Vng3Stl5zOuGhN4+RuP2NWf5snJCeGUKA7pzAvcHU=
398398
github.com/fluxcd/pkg/helmtestserver v0.13.1 h1:SjEk9QaMWMjwnqTXGtfMeorC5H+KDvV2YK87Sr2dFng=

internal/controller/gitrepository_controller.go

Lines changed: 60 additions & 12 deletions
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/runtime/logger"
31+
"github.com/go-git/go-git/v5/plumbing/transport"
3132
corev1 "k8s.io/api/core/v1"
3233
"k8s.io/apimachinery/pkg/runtime"
3334
"k8s.io/apimachinery/pkg/types"
@@ -473,24 +474,35 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
473474
conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
474475
}
475476

477+
var proxyOpts *transport.ProxyOptions
478+
if obj.Spec.ProxySecretRef != nil {
479+
var err error
480+
proxyOpts, err = r.getProxyOpts(ctx, obj.Spec.ProxySecretRef.Name, obj.GetNamespace())
481+
if err != nil {
482+
e := serror.NewGeneric(
483+
fmt.Errorf("failed to configure proxy options: %w", err),
484+
sourcev1.AuthenticationFailedReason,
485+
)
486+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
487+
// Return error as the world as observed may change
488+
return sreconcile.ResultEmpty, e
489+
}
490+
}
491+
476492
var authData map[string][]byte
477493
if obj.Spec.SecretRef != nil {
478494
// Attempt to retrieve secret
479-
name := types.NamespacedName{
480-
Namespace: obj.GetNamespace(),
481-
Name: obj.Spec.SecretRef.Name,
482-
}
483-
var secret corev1.Secret
484-
if err := r.Client.Get(ctx, name, &secret); err != nil {
495+
var err error
496+
authData, err = r.getSecretData(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
497+
if err != nil {
485498
e := serror.NewGeneric(
486-
fmt.Errorf("failed to get secret '%s': %w", name.String(), err),
499+
fmt.Errorf("failed to get secret '%s/%s': %w", obj.GetNamespace(), obj.Spec.SecretRef.Name, err),
487500
sourcev1.AuthenticationFailedReason,
488501
)
489502
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
490503
// Return error as the world as observed may change
491504
return sreconcile.ResultEmpty, e
492505
}
493-
authData = secret.Data
494506
}
495507

496508
u, err := url.Parse(obj.Spec.URL)
@@ -536,7 +548,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
536548
// Persist the ArtifactSet.
537549
*includes = *artifacts
538550

539-
c, err := r.gitCheckout(ctx, obj, authOpts, dir, true)
551+
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, true)
540552
if err != nil {
541553
return sreconcile.ResultEmpty, err
542554
}
@@ -578,7 +590,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
578590

579591
// If we can't skip the reconciliation, checkout again without any
580592
// optimization.
581-
c, err := r.gitCheckout(ctx, obj, authOpts, dir, false)
593+
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, false)
582594
if err != nil {
583595
return sreconcile.ResultEmpty, err
584596
}
@@ -606,6 +618,39 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
606618
return sreconcile.ResultSuccess, nil
607619
}
608620

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, error) {
625+
proxyData, err := r.getSecretData(ctx, proxySecretName, proxySecretNamespace)
626+
if err != nil {
627+
return nil, fmt.Errorf("failed to get secret '%s/%s': %w", proxySecretNamespace, proxySecretName, err)
628+
}
629+
address, ok := proxyData["address"]
630+
if !ok {
631+
return nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", proxySecretNamespace, proxySecretName)
632+
}
633+
634+
proxyOpts := &transport.ProxyOptions{
635+
URL: string(address),
636+
Username: string(proxyData["username"]),
637+
Password: string(proxyData["password"]),
638+
}
639+
return proxyOpts, nil
640+
}
641+
642+
func (r *GitRepositoryReconciler) getSecretData(ctx context.Context, name, namespace string) (map[string][]byte, error) {
643+
key := types.NamespacedName{
644+
Namespace: namespace,
645+
Name: name,
646+
}
647+
var secret corev1.Secret
648+
if err := r.Client.Get(ctx, key, &secret); err != nil {
649+
return nil, err
650+
}
651+
return secret.Data, nil
652+
}
653+
609654
// reconcileArtifact archives a new Artifact to the Storage, if the current
610655
// (Status) data on the object does not match the given.
611656
//
@@ -776,8 +821,8 @@ func (r *GitRepositoryReconciler) reconcileInclude(ctx context.Context, sp *patc
776821

777822
// gitCheckout builds checkout options with the given configurations and
778823
// performs a git checkout.
779-
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context,
780-
obj *sourcev1.GitRepository, authOpts *git.AuthOptions, dir string, optimized bool) (*git.Commit, error) {
824+
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context, obj *sourcev1.GitRepository,
825+
authOpts *git.AuthOptions, proxyOpts *transport.ProxyOptions, dir string, optimized bool) (*git.Commit, error) {
781826
// Configure checkout strategy.
782827
cloneOpts := repository.CloneConfig{
783828
RecurseSubmodules: obj.Spec.RecurseSubmodules,
@@ -807,6 +852,9 @@ func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context,
807852
if authOpts.Transport == git.HTTP {
808853
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
809854
}
855+
if proxyOpts != nil {
856+
clientOpts = append(clientOpts, gogit.WithProxy(*proxyOpts))
857+
}
810858

811859
gitReader, err := gogit.NewClient(dir, authOpts, clientOpts...)
812860
if err != nil {

internal/controller/gitrepository_controller_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/go-git/go-git/v5/config"
3434
"github.com/go-git/go-git/v5/plumbing"
3535
"github.com/go-git/go-git/v5/plumbing/object"
36+
"github.com/go-git/go-git/v5/plumbing/transport"
3637
"github.com/go-git/go-git/v5/storage/memory"
3738
. "github.com/onsi/gomega"
3839
sshtestdata "golang.org/x/crypto/ssh/testdata"
@@ -1624,6 +1625,76 @@ func TestGitRepositoryReconciler_verifyCommitSignature(t *testing.T) {
16241625
}
16251626
}
16261627

1628+
func TestGitRepositoryReconciler_getProxyOpts(t *testing.T) {
1629+
invalidProxy := &corev1.Secret{
1630+
ObjectMeta: metav1.ObjectMeta{
1631+
Name: "invalid-proxy",
1632+
},
1633+
Data: map[string][]byte{
1634+
"url": []byte("https://example.com"),
1635+
},
1636+
}
1637+
validProxy := &corev1.Secret{
1638+
ObjectMeta: metav1.ObjectMeta{
1639+
Name: "valid-proxy",
1640+
},
1641+
Data: map[string][]byte{
1642+
"address": []byte("https://example.com"),
1643+
"username": []byte("user"),
1644+
"password": []byte("pass"),
1645+
},
1646+
}
1647+
1648+
clientBuilder := fakeclient.NewClientBuilder().
1649+
WithScheme(testEnv.GetScheme())
1650+
clientBuilder.WithObjects(invalidProxy, validProxy)
1651+
1652+
r := &GitRepositoryReconciler{
1653+
Client: clientBuilder.Build(),
1654+
}
1655+
1656+
tests := []struct {
1657+
name string
1658+
secret string
1659+
err string
1660+
proxyOpts *transport.ProxyOptions
1661+
}{
1662+
{
1663+
name: "non-existent secret",
1664+
secret: "non-existent",
1665+
err: "failed to get secret '/non-existent': ",
1666+
},
1667+
{
1668+
name: "invalid proxy secret",
1669+
secret: "invalid-proxy",
1670+
err: "invalid proxy secret '/invalid-proxy': key 'address' is missing",
1671+
},
1672+
{
1673+
name: "valid proxy secret",
1674+
secret: "valid-proxy",
1675+
proxyOpts: &transport.ProxyOptions{
1676+
URL: "https://example.com",
1677+
Username: "user",
1678+
Password: "pass",
1679+
},
1680+
},
1681+
}
1682+
1683+
for _, tt := range tests {
1684+
t.Run(tt.name, func(t *testing.T) {
1685+
g := NewWithT(t)
1686+
opts, err := r.getProxyOpts(context.TODO(), tt.secret, "")
1687+
if opts != nil {
1688+
g.Expect(err).ToNot(HaveOccurred())
1689+
g.Expect(opts).To(Equal(tt.proxyOpts))
1690+
} else {
1691+
g.Expect(err).To(HaveOccurred())
1692+
g.Expect(err.Error()).To(ContainSubstring(tt.err))
1693+
}
1694+
})
1695+
}
1696+
}
1697+
16271698
func TestGitRepositoryReconciler_ConditionsUpdate(t *testing.T) {
16281699
g := NewWithT(t)
16291700

0 commit comments

Comments
 (0)