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

Add Custom Git Client TLS config for WAF-Secured Repos #1302

Open
wants to merge 4 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
31 changes: 27 additions & 4 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,39 @@ make deploy
Create a `.vscode/launch.json` file:
```json
{
"version": "0.2.0",
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"name": "Debug Test Function",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}/internal/controller/",
"env": {
"HTTPS_PROXY": "",
"HTTP_PROXY": "",
"KUBEBUILDER_ASSETS": "${workspaceFolder}/build/testbin/k8s/1.28.0-linux-amd64/",
"GIT_CONFIG_GLOBAL":"/dev/null",
"GIT_CONFIG_NOSYSTEM":"true",

},
"args": [
"-test.run", "^.*",
"-test.v"
]
},
{
"name": "Debug",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go"
"program": "${workspaceFolder}/main.go",
"args": [
"--storage-adv-addr=:0",
"--storage-path=/tmp/"
]
}
]
],
}
```

Expand Down
45 changes: 45 additions & 0 deletions internal/controller/auth_certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package controller

import (
"context"
"crypto/tls"
"net/http"
"net/url"

"github.com/go-git/go-git/v5/plumbing/transport"
httptransport "github.com/go-git/go-git/v5/plumbing/transport/http"
ctrl "sigs.k8s.io/controller-runtime"
)

// HttpTransportWithCustomCerts returns an HTTP transport with custom certificates.
// If proxyStr is provided, it will be used as the proxy URL.
// If not, it tries to fetch the proxy from an environment variable.
func HttpTransportwithCustomCerts(tlsConfig *tls.Config, proxyStr *transport.ProxyOptions, ctx context.Context) (transport.Transport, error) {

log := ctrl.LoggerFrom(ctx)
// var message string

var (
proxyUrl *url.URL
err error
)
if proxyStr != nil {
proxyUrl, err = url.Parse(proxyStr.URL)
if err != nil {
log.Info("failed to parse proxy url: %w", err)
}
}

if tlsConfig == nil || len(tlsConfig.Certificates) == 0 {
log.Info("tlsConfig cannot be nil")
return nil, nil
}

return httptransport.NewClient(&http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
TLSClientConfig: tlsConfig,
},
}), nil

}
104 changes: 101 additions & 3 deletions internal/controller/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ import (
"github.com/fluxcd/source-controller/internal/features"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
"github.com/fluxcd/source-controller/internal/tls"
"github.com/fluxcd/source-controller/internal/util"
gitclient "github.com/go-git/go-git/v5/plumbing/transport/client"
)

// gitRepositoryReadyCondition contains the information required to summarize a
Expand Down Expand Up @@ -148,6 +150,94 @@ func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
return r.SetupWithManagerAndOptions(mgr, GitRepositoryReconcilerOptions{})
}

// Interface co configure gitclient with custom TLS options
// used for application firewall authentication.
type GitClientConfigurer interface {
ConfigureGitClient(ctx context.Context, obj *sourcev1.GitRepository)
IsValid() bool
}

type GitClientHttpConfigurer struct {
SSLCertificateData map[string][]byte
ProxyOpts *transport.ProxyOptions
Valid bool
DefaultTransport transport.Transport
AppFirewallTransport transport.Transport
}

func (c *GitClientHttpConfigurer) IsValid() bool {
return c.Valid
}

func (c *GitClientHttpConfigurer) SetValid() {
c.Valid = true
}

func (c *GitClientHttpConfigurer) SetInvalid() {
c.Valid = false
}

func (r *GitRepositoryReconciler) isCertificateDataValid(sslCertificateData map[string][]byte) bool {
certBytes, keyBytes := sslCertificateData["certFile"], sslCertificateData["keyFile"]
// Validate that both the certificate and key data are present
return len(certBytes) > 0 && len(keyBytes) > 0
}


func (h *GitClientHttpConfigurer) ConfigureGitClient(ctx context.Context, obj *sourcev1.GitRepository) {

if obj.Spec.SecretRef != nil {
// var secretName = obj.Spec.SecretRef.Name
// if secretName == "waf-authentication" {
sslCertificate := &corev1.Secret{
Data: h.SSLCertificateData,
}
tlsConfig, _, err := tls.TLSClientConfigFromSecret(*sslCertificate, "")
if err != nil {
fmt.Println("Error generating TLS config:", err)
return
}


transportHttp, err := HttpTransportwithCustomCerts(tlsConfig, h.ProxyOpts, ctx)
if err != nil {
fmt.Println("Error setting up transport:", err)
return
}

gitclient.InstallProtocol("https", transportHttp)

}
// }
}

// configureHttpTransport sets up the HTTP transport configuration for the Git client.
func (r *GitRepositoryReconciler) configureHttpTransport(ctx context.Context, obj *sourcev1.GitRepository) (*GitClientHttpConfigurer, error) {
httpTransportConfig := &GitClientHttpConfigurer{} // Initialize with defaults configuration

// Check if SecretRef is specified for the repository
if obj.Spec.SecretRef != nil {
// Fetch the SSL certificate data from the specified secret
sslCertificateData, err := r.getSecretData(ctx, obj.Spec.SecretRef.Name, obj.Namespace)
if err != nil {
return nil, fmt.Errorf("failed to get secret '%s/%s': %w", obj.Namespace, obj.Spec.SecretRef.Name, err)
}

// Set up the HTTP transport configuration with the fetched certificate data
httpTransportConfig.SSLCertificateData = sslCertificateData
if r.isCertificateDataValid(sslCertificateData) {
httpTransportConfig.SetValid()
} else {
httpTransportConfig.SetInvalid()
}
} else {
// If no SecretRef is provided, mark the transport config as invalid or set defaults
httpTransportConfig.SetInvalid()
}

return httpTransportConfig, nil
}

func (r *GitRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts GitRepositoryReconcilerOptions) error {
r.patchOptions = getPatchOptions(gitRepositoryReadyCondition.Owned, r.ControllerName)

Expand Down Expand Up @@ -535,7 +625,12 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
// Persist the ArtifactSet.
*includes = *artifacts

c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, true)
httpTransportConfig, err := r.configureHttpTransport(ctx, obj)
if err != nil {
return sreconcile.ResultEmpty, err
}

c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, true, httpTransportConfig)
if err != nil {
return sreconcile.ResultEmpty, err
}
Expand Down Expand Up @@ -579,7 +674,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch

// If we can't skip the reconciliation, checkout again without any
// optimization.
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, false)
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, false, httpTransportConfig)
if err != nil {
return sreconcile.ResultEmpty, err
}
Expand Down Expand Up @@ -832,7 +927,7 @@ func (r *GitRepositoryReconciler) reconcileInclude(ctx context.Context, sp *patc
// gitCheckout builds checkout options with the given configurations and
// performs a git checkout.
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context, obj *sourcev1.GitRepository,
authOpts *git.AuthOptions, proxyOpts *transport.ProxyOptions, dir string, optimized bool) (*git.Commit, error) {
authOpts *git.AuthOptions, proxyOpts *transport.ProxyOptions, dir string, optimized bool, clientConf GitClientConfigurer) (*git.Commit, error) {
// Configure checkout strategy.
cloneOpts := repository.CloneConfig{
RecurseSubmodules: obj.Spec.RecurseSubmodules,
Expand Down Expand Up @@ -866,6 +961,9 @@ func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context, obj *sourcev1
clientOpts = append(clientOpts, gogit.WithProxy(*proxyOpts))
}

if clientConf.IsValid() {
clientConf.ConfigureGitClient(ctx, obj)
}
gitReader, err := gogit.NewClient(dir, authOpts, clientOpts...)
if err != nil {
e := serror.NewGeneric(
Expand Down
27 changes: 27 additions & 0 deletions internal/controller/gitrepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,33 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new upstream revision 'master@sha1:<commit>'"),
},
},
{
name: "HTTPS with TLS certs authentication with WAF Reconciling=True",
protocol: "https",
server: options{
publicKey: tlsPublicKey,
privateKey: tlsPrivateKey,
ca: tlsCA,
},
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "waf-authentication",
},
Data: map[string][]byte{
"certFile": clientPublicKey,
"keyFile": clientPrivateKey,
"caFile": tlsCA,
},
},
beforeFunc: func(obj *sourcev1.GitRepository) {
obj.Spec.SecretRef = &meta.LocalObjectReference{Name: "waf-authentication"}
},
want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new upstream revision 'master@sha1:<commit>'"),
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new upstream revision 'master@sha1:<commit>'"),
},
},
}

for _, tt := range tests {
Expand Down