From e98f1142a6d0135d7e586bd4c06fb98eefe9b604 Mon Sep 17 00:00:00 2001
From: Max Jonas Werner <mail@makk.es>
Date: Wed, 1 Sep 2021 15:33:43 +0200
Subject: [PATCH] feat: enable bootstrap with custom CA locally

When a user provided the `--ca-file` flag to the `bootstrap` command,
the given CA file wasn't taken into account for cloning the repository
locally. It was just passed along to the CR that is created so Flux
can make use of it when cloning the repository in-cluster.

However, users may not want to add a custom CA to their local host's
trust chain and may expect the `--ca-file` flag to be respected also
for cloning the repository locally. This is what this commit
accomplishes.

closes #1775

Signed-off-by: Max Jonas Werner <mail@makk.es>
---
 cmd/flux/bootstrap_git.go                 | 11 ++++++++++
 internal/bootstrap/bootstrap.go           |  4 ++--
 internal/bootstrap/bootstrap_plain_git.go | 25 ++++++++++++++++-------
 internal/bootstrap/git/git.go             |  4 ++--
 internal/bootstrap/git/gogit/gogit.go     |  6 ++++--
 5 files changed, 37 insertions(+), 13 deletions(-)

diff --git a/cmd/flux/bootstrap_git.go b/cmd/flux/bootstrap_git.go
index 1a583a2a..90a2148a 100644
--- a/cmd/flux/bootstrap_git.go
+++ b/cmd/flux/bootstrap_git.go
@@ -19,6 +19,7 @@ package main
 import (
 	"context"
 	"fmt"
+	"io/ioutil"
 	"net/url"
 	"os"
 	"strings"
@@ -199,6 +200,15 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
 		RecurseSubmodules: bootstrapArgs.recurseSubmodules,
 	}
 
+	var caBundle []byte
+	if bootstrapArgs.caFile != "" {
+		var err error
+		caBundle, err = ioutil.ReadFile(bootstrapArgs.caFile)
+		if err != nil {
+			return fmt.Errorf("unable to read TLS CA file: %w", err)
+		}
+	}
+
 	// Bootstrap config
 	bootstrapOpts := []bootstrap.GitOption{
 		bootstrap.WithRepositoryURL(gitArgs.url),
@@ -208,6 +218,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
 		bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
 		bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
 		bootstrap.WithLogger(logger),
+		bootstrap.WithCABundle(caBundle),
 	}
 
 	// Setup bootstrapper with constructed configs
diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go
index 34d54840..91a9fa3c 100644
--- a/internal/bootstrap/bootstrap.go
+++ b/internal/bootstrap/bootstrap.go
@@ -47,7 +47,7 @@ type Reconciler interface {
 	// manifests with the provided values, committing them to Git and
 	// pushing to remote if there are any changes, and applying them
 	// to the cluster.
-	ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options) error
+	ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options, secretOpts sourcesecret.Options) error
 
 	// ReconcileSourceSecret reconciles the source secret by generating
 	// a new secret with the provided values if the secret does not
@@ -87,7 +87,7 @@ func Run(ctx context.Context, reconciler Reconciler, manifestsBase string,
 		}
 	}
 
-	if err := reconciler.ReconcileComponents(ctx, manifestsBase, installOpts); err != nil {
+	if err := reconciler.ReconcileComponents(ctx, manifestsBase, installOpts, secretOpts); err != nil {
 		return err
 	}
 	if err := reconciler.ReconcileSourceSecret(ctx, secretOpts); err != nil {
diff --git a/internal/bootstrap/bootstrap_plain_git.go b/internal/bootstrap/bootstrap_plain_git.go
index e0018b89..6475c40f 100644
--- a/internal/bootstrap/bootstrap_plain_git.go
+++ b/internal/bootstrap/bootstrap_plain_git.go
@@ -46,8 +46,9 @@ import (
 )
 
 type PlainGitBootstrapper struct {
-	url    string
-	branch string
+	url      string
+	branch   string
+	caBundle []byte
 
 	author                git.Author
 	commitMessageAppendix string
@@ -70,6 +71,16 @@ func WithRepositoryURL(url string) GitOption {
 	return repositoryURLOption(url)
 }
 
+func WithCABundle(b []byte) GitOption {
+	return caBundleOption(b)
+}
+
+type caBundleOption []byte
+
+func (o caBundleOption) applyGit(b *PlainGitBootstrapper) {
+	b.caBundle = o
+}
+
 type repositoryURLOption string
 
 func (o repositoryURLOption) applyGit(b *PlainGitBootstrapper) {
@@ -97,7 +108,7 @@ func NewPlainGitProvider(git git.Git, kube client.Client, opts ...GitOption) (*P
 	return b, nil
 }
 
-func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options) error {
+func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options, secretOpts sourcesecret.Options) error {
 	// Clone if not already
 	if _, err := b.git.Status(); err != nil {
 		if err != git.ErrNoGitRepository {
@@ -107,7 +118,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
 		b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
 		var cloned bool
 		if err = retry(1, 2*time.Second, func() (err error) {
-			cloned, err = b.git.Clone(ctx, b.url, b.branch)
+			cloned, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
 			return
 		}); err != nil {
 			return fmt.Errorf("failed to clone repository: %w", err)
@@ -145,7 +156,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
 	if err == nil {
 		b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
 		b.logger.Actionf("pushing component manifests to %q", b.url)
-		if err = b.git.Push(ctx); err != nil {
+		if err = b.git.Push(ctx, b.caBundle); err != nil {
 			return fmt.Errorf("failed to push manifests: %w", err)
 		}
 	} else {
@@ -260,7 +271,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
 			b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
 			var cloned bool
 			if err = retry(1, 2*time.Second, func() (err error) {
-				cloned, err = b.git.Clone(ctx, b.url, b.branch)
+				cloned, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
 				return
 			}); err != nil {
 				return fmt.Errorf("failed to clone repository: %w", err)
@@ -309,7 +320,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
 	if err == nil {
 		b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
 		b.logger.Actionf("pushing sync manifests to %q", b.url)
-		if err = b.git.Push(ctx); err != nil {
+		if err = b.git.Push(ctx, b.caBundle); err != nil {
 			return fmt.Errorf("failed to push sync manifests: %w", err)
 		}
 	} else {
diff --git a/internal/bootstrap/git/git.go b/internal/bootstrap/git/git.go
index 86466e79..103ea49b 100644
--- a/internal/bootstrap/git/git.go
+++ b/internal/bootstrap/git/git.go
@@ -42,10 +42,10 @@ type Commit struct {
 // remote repository.
 type Git interface {
 	Init(url, branch string) (bool, error)
-	Clone(ctx context.Context, url, branch string) (bool, error)
+	Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error)
 	Write(path string, reader io.Reader) error
 	Commit(message Commit) (string, error)
-	Push(ctx context.Context) error
+	Push(ctx context.Context, caBundle []byte) error
 	Status() (bool, error)
 	Head() (string, error)
 	Path() string
diff --git a/internal/bootstrap/git/gogit/gogit.go b/internal/bootstrap/git/gogit/gogit.go
index b11acd0c..07248791 100644
--- a/internal/bootstrap/git/gogit/gogit.go
+++ b/internal/bootstrap/git/gogit/gogit.go
@@ -82,7 +82,7 @@ func (g *GoGit) Init(url, branch string) (bool, error) {
 	return true, nil
 }
 
-func (g *GoGit) Clone(ctx context.Context, url, branch string) (bool, error) {
+func (g *GoGit) Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error) {
 	branchRef := plumbing.NewBranchReferenceName(branch)
 	r, err := gogit.PlainCloneContext(ctx, g.path, false, &gogit.CloneOptions{
 		URL:           url,
@@ -94,6 +94,7 @@ func (g *GoGit) Clone(ctx context.Context, url, branch string) (bool, error) {
 		NoCheckout: false,
 		Progress:   nil,
 		Tags:       gogit.NoTags,
+		CABundle:   caBundle,
 	})
 	if err != nil {
 		if err == transport.ErrEmptyRemoteRepository || isRemoteBranchNotFoundErr(err, branchRef.String()) {
@@ -185,7 +186,7 @@ func (g *GoGit) Commit(message git.Commit) (string, error) {
 	return commit.String(), nil
 }
 
-func (g *GoGit) Push(ctx context.Context) error {
+func (g *GoGit) Push(ctx context.Context, caBundle []byte) error {
 	if g.repository == nil {
 		return git.ErrNoGitRepository
 	}
@@ -194,6 +195,7 @@ func (g *GoGit) Push(ctx context.Context) error {
 		RemoteName: gogit.DefaultRemoteName,
 		Auth:       g.auth,
 		Progress:   nil,
+		CABundle:   caBundle,
 	})
 }
 
-- 
GitLab