From 7792cd6a105427e240af17056bc4662774257271 Mon Sep 17 00:00:00 2001
From: Stefan Prodan <stefan.prodan@gmail.com>
Date: Wed, 11 Nov 2020 15:00:52 +0200
Subject: [PATCH] Add token authentication option to bootstrap

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
---
 cmd/flux/bootstrap.go             |  5 +-
 cmd/flux/bootstrap_github.go      | 90 ++++++++++++++++++++-----------
 cmd/flux/bootstrap_gitlab.go      | 44 +++++++--------
 docs/cmd/flux_bootstrap.md        |  1 +
 docs/cmd/flux_bootstrap_github.md | 29 +++++-----
 docs/cmd/flux_bootstrap_gitlab.md | 13 ++---
 docs/guides/installation.md       | 15 ++++++
 7 files changed, 125 insertions(+), 72 deletions(-)

diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go
index 47b01127..1cef362e 100644
--- a/cmd/flux/bootstrap.go
+++ b/cmd/flux/bootstrap.go
@@ -57,6 +57,7 @@ var (
 	bootstrapArch               = flags.Arch(defaults.Arch)
 	bootstrapLogLevel           = flags.LogLevel(defaults.LogLevel)
 	bootstrapRequiredComponents = []string{"source-controller", "kustomize-controller"}
+	bootstrapTokenAuth          bool
 )
 
 const (
@@ -75,14 +76,16 @@ func init() {
 	bootstrapCmd.PersistentFlags().Var(&bootstrapArch, "arch", bootstrapArch.Description())
 	bootstrapCmd.PersistentFlags().StringVar(&bootstrapBranch, "branch", bootstrapDefaultBranch,
 		"default branch (for GitHub this must match the default branch setting for the organization)")
-	rootCmd.AddCommand(bootstrapCmd)
 	bootstrapCmd.PersistentFlags().BoolVar(&bootstrapWatchAllNamespaces, "watch-all-namespaces", true,
 		"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
 	bootstrapCmd.PersistentFlags().BoolVar(&bootstrapNetworkPolicy, "network-policy", true,
 		"deny ingress access to the toolkit controllers from other namespaces using network policies")
+	bootstrapCmd.PersistentFlags().BoolVar(&bootstrapTokenAuth, "token-auth", false,
+		"when enabled, the personal access token will be used instead of SSH deploy key")
 	bootstrapCmd.PersistentFlags().Var(&bootstrapLogLevel, "log-level", bootstrapLogLevel.Description())
 	bootstrapCmd.PersistentFlags().StringVar(&bootstrapManifestsPath, "manifests", "", "path to the manifest directory")
 	bootstrapCmd.PersistentFlags().MarkHidden("manifests")
+	rootCmd.AddCommand(bootstrapCmd)
 }
 
 func bootstrapValidate() error {
diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go
index 5565a98a..0fd0a1aa 100644
--- a/cmd/flux/bootstrap_github.go
+++ b/cmd/flux/bootstrap_github.go
@@ -26,6 +26,8 @@ import (
 	"time"
 
 	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/fluxcd/flux2/internal/utils"
 	"github.com/fluxcd/pkg/git"
@@ -35,7 +37,7 @@ var bootstrapGitHubCmd = &cobra.Command{
 	Use:   "github",
 	Short: "Bootstrap toolkit components in a GitHub repository",
 	Long: `The bootstrap github command creates the GitHub repository if it doesn't exists and
-commits the toolkit components manifests to the master branch.
+commits the toolkit components manifests to the main branch.
 Then it configures the target cluster to synchronize with the repository.
 If the toolkit components are present on the cluster,
 the bootstrap command will perform an upgrade if needed.`,
@@ -54,8 +56,11 @@ the bootstrap command will perform an upgrade if needed.`,
   # Run bootstrap for a public repository on a personal account
   flux bootstrap github --owner=<user> --repository=<repo name> --private=false --personal=true 
 
-  # Run bootstrap for a private repo hosted on GitHub Enterprise
-  flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain>
+  # Run bootstrap for a private repo hosted on GitHub Enterprise using SSH auth
+  flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain> --ssh-hostname=<domain>
+
+  # Run bootstrap for a private repo hosted on GitHub Enterprise using HTTPS auth
+  flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain> --token-auth
 
   # Run bootstrap for a an existing repository with a branch named main
   flux bootstrap github --owner=<organization> --repository=<repo name> --branch=main
@@ -64,15 +69,16 @@ the bootstrap command will perform an upgrade if needed.`,
 }
 
 var (
-	ghOwner      string
-	ghRepository string
-	ghInterval   time.Duration
-	ghPersonal   bool
-	ghPrivate    bool
-	ghHostname   string
-	ghPath       string
-	ghTeams      []string
-	ghDelete     bool
+	ghOwner       string
+	ghRepository  string
+	ghInterval    time.Duration
+	ghPersonal    bool
+	ghPrivate     bool
+	ghHostname    string
+	ghPath        string
+	ghTeams       []string
+	ghDelete      bool
+	ghSSHHostname string
 )
 
 const (
@@ -87,6 +93,7 @@ func init() {
 	bootstrapGitHubCmd.Flags().BoolVar(&ghPrivate, "private", true, "is private repository")
 	bootstrapGitHubCmd.Flags().DurationVar(&ghInterval, "interval", time.Minute, "sync interval")
 	bootstrapGitHubCmd.Flags().StringVar(&ghHostname, "hostname", git.GitHubDefaultHostname, "GitHub hostname")
+	bootstrapGitHubCmd.Flags().StringVar(&ghSSHHostname, "ssh-hostname", "", "GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one")
 	bootstrapGitHubCmd.Flags().StringVar(&ghPath, "path", "", "repository path, when specified the cluster sync will be scoped to this path")
 
 	bootstrapGitHubCmd.Flags().BoolVar(&ghDelete, "delete", false, "delete repository (used for testing only)")
@@ -110,6 +117,10 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
+	if ghSSHHostname != "" {
+		repository.SSHHost = ghSSHHostname
+	}
+
 	provider := &git.GithubProvider{
 		IsPrivate:  ghPrivate,
 		IsPersonal: ghPersonal,
@@ -155,7 +166,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 		}
 	}
 
-	// clone repository and checkout the master branch
+	// clone repository and checkout the main branch
 	if err := repository.Checkout(ctx, bootstrapBranch, tmpDir); err != nil {
 		return err
 	}
@@ -201,28 +212,45 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 		logger.Successf("install completed")
 	}
 
-	// setup SSH deploy key
-	if shouldCreateDeployKey(ctx, kubeClient, namespace) {
-		logger.Actionf("configuring deploy key")
-		u, err := url.Parse(repository.GetSSH())
-		if err != nil {
-			return fmt.Errorf("git URL parse failed: %w", err)
+	if bootstrapTokenAuth {
+		// setup HTTPS token auth
+		secret := corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      namespace,
+				Namespace: namespace,
+			},
+			StringData: map[string]string{
+				"username": "git",
+				"password": ghToken,
+			},
 		}
-
-		key, err := generateDeployKey(ctx, kubeClient, u, namespace)
-		if err != nil {
-			return fmt.Errorf("generating deploy key failed: %w", err)
+		if err := upsertSecret(ctx, kubeClient, secret); err != nil {
+			return err
 		}
+	} else {
+		// setup SSH deploy key
+		if shouldCreateDeployKey(ctx, kubeClient, namespace) {
+			logger.Actionf("configuring deploy key")
+			u, err := url.Parse(repository.GetSSH())
+			if err != nil {
+				return fmt.Errorf("git URL parse failed: %w", err)
+			}
 
-		keyName := "flux"
-		if ghPath != "" {
-			keyName = fmt.Sprintf("flux-%s", ghPath)
-		}
+			key, err := generateDeployKey(ctx, kubeClient, u, namespace)
+			if err != nil {
+				return fmt.Errorf("generating deploy key failed: %w", err)
+			}
 
-		if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
-			return err
-		} else if changed {
-			logger.Successf("deploy key configured")
+			keyName := "flux"
+			if ghPath != "" {
+				keyName = fmt.Sprintf("flux-%s", ghPath)
+			}
+
+			if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
+				return err
+			} else if changed {
+				logger.Successf("deploy key configured")
+			}
 		}
 	}
 
diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go
index 5d504def..546d93fb 100644
--- a/cmd/flux/bootstrap_gitlab.go
+++ b/cmd/flux/bootstrap_gitlab.go
@@ -45,22 +45,22 @@ the bootstrap command will perform an upgrade if needed.`,
   export GITLAB_TOKEN=<my-token>
 
   # Run bootstrap for a private repo using HTTPS token authentication 
-  flux bootstrap gitlab --owner=<group> --repository=<repo name>
+  flux bootstrap gitlab --owner=<group> --repository=<repo name> --token-auth
 
   # Run bootstrap for a private repo using SSH authentication
-  flux bootstrap gitlab --owner=<group> --repository=<repo name> --ssh-hostname=gitlab.com
+  flux bootstrap gitlab --owner=<group> --repository=<repo name>
 
   # Run bootstrap for a repository path
   flux bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
 
   # Run bootstrap for a public repository on a personal account
-  flux bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
+  flux bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal --token-auth
 
   # Run bootstrap for a private repo hosted on a GitLab server 
-  flux bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain>
+  flux bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain> --token-auth
 
   # Run bootstrap for a an existing repository with a branch named main
-  flux bootstrap gitlab --owner=<organization> --repository=<repo name> --branch=main
+  flux bootstrap gitlab --owner=<organization> --repository=<repo name> --branch=main --token-auth
 `,
 	RunE: bootstrapGitLabCmdRun,
 }
@@ -83,7 +83,7 @@ func init() {
 	bootstrapGitLabCmd.Flags().BoolVar(&glPrivate, "private", true, "is private repository")
 	bootstrapGitLabCmd.Flags().DurationVar(&glInterval, "interval", time.Minute, "sync interval")
 	bootstrapGitLabCmd.Flags().StringVar(&glHostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname")
-	bootstrapGitLabCmd.Flags().StringVar(&glSSHHostname, "ssh-hostname", "", "GitLab SSH hostname, when specified a deploy key will be added to the repository")
+	bootstrapGitLabCmd.Flags().StringVar(&glSSHHostname, "ssh-hostname", "", "GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one")
 	bootstrapGitLabCmd.Flags().StringVar(&glPath, "path", "", "repository path, when specified the cluster sync will be scoped to this path")
 
 	bootstrapCmd.AddCommand(bootstrapGitLabCmd)
@@ -180,7 +180,22 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
 
 	repoURL := repository.GetURL()
 
-	if glSSHHostname != "" {
+	if bootstrapTokenAuth {
+		// setup HTTPS token auth
+		secret := corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      namespace,
+				Namespace: namespace,
+			},
+			StringData: map[string]string{
+				"username": "git",
+				"password": glToken,
+			},
+		}
+		if err := upsertSecret(ctx, kubeClient, secret); err != nil {
+			return err
+		}
+	} else {
 		// setup SSH deploy key
 		repoURL = repository.GetSSH()
 		if shouldCreateDeployKey(ctx, kubeClient, namespace) {
@@ -206,21 +221,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
 				logger.Successf("deploy key configured")
 			}
 		}
-	} else {
-		// setup HTTPS token auth
-		secret := corev1.Secret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      namespace,
-				Namespace: namespace,
-			},
-			StringData: map[string]string{
-				"username": "git",
-				"password": glToken,
-			},
-		}
-		if err := upsertSecret(ctx, kubeClient, secret); err != nil {
-			return err
-		}
 	}
 
 	// configure repo synchronization
diff --git a/docs/cmd/flux_bootstrap.md b/docs/cmd/flux_bootstrap.md
index 5a74161a..4d1f20b0 100644
--- a/docs/cmd/flux_bootstrap.md
+++ b/docs/cmd/flux_bootstrap.md
@@ -17,6 +17,7 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git
       --log-level logLevel         log level, available options are: (debug, info, error) (default info)
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
+      --token-auth                 when enabled, the personal access token will be used instead of SSH deploy key
   -v, --version string             toolkit version (default "latest")
       --watch-all-namespaces       watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
 ```
diff --git a/docs/cmd/flux_bootstrap_github.md b/docs/cmd/flux_bootstrap_github.md
index a105a216..4186515a 100644
--- a/docs/cmd/flux_bootstrap_github.md
+++ b/docs/cmd/flux_bootstrap_github.md
@@ -5,7 +5,7 @@ Bootstrap toolkit components in a GitHub repository
 ### Synopsis
 
 The bootstrap github command creates the GitHub repository if it doesn't exists and
-commits the toolkit components manifests to the master branch.
+commits the toolkit components manifests to the main branch.
 Then it configures the target cluster to synchronize with the repository.
 If the toolkit components are present on the cluster,
 the bootstrap command will perform an upgrade if needed.
@@ -32,8 +32,11 @@ flux bootstrap github [flags]
   # Run bootstrap for a public repository on a personal account
   flux bootstrap github --owner=<user> --repository=<repo name> --private=false --personal=true 
 
-  # Run bootstrap for a private repo hosted on GitHub Enterprise
-  flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain>
+  # Run bootstrap for a private repo hosted on GitHub Enterprise using SSH auth
+  flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain> --ssh-hostname=<domain>
+
+  # Run bootstrap for a private repo hosted on GitHub Enterprise using HTTPS auth
+  flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain> --token-auth
 
   # Run bootstrap for a an existing repository with a branch named main
   flux bootstrap github --owner=<organization> --repository=<repo name> --branch=main
@@ -43,15 +46,16 @@ flux bootstrap github [flags]
 ### Options
 
 ```
-  -h, --help                help for github
-      --hostname string     GitHub hostname (default "github.com")
-      --interval duration   sync interval (default 1m0s)
-      --owner string        GitHub user or organization name
-      --path string         repository path, when specified the cluster sync will be scoped to this path
-      --personal            is personal repository
-      --private             is private repository (default true)
-      --repository string   GitHub repository name
-      --team stringArray    GitHub team to be given maintainer access
+  -h, --help                  help for github
+      --hostname string       GitHub hostname (default "github.com")
+      --interval duration     sync interval (default 1m0s)
+      --owner string          GitHub user or organization name
+      --path string           repository path, when specified the cluster sync will be scoped to this path
+      --personal              is personal repository
+      --private               is private repository (default true)
+      --repository string     GitHub repository name
+      --ssh-hostname string   GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one
+      --team stringArray      GitHub team to be given maintainer access
 ```
 
 ### Options inherited from parent commands
@@ -68,6 +72,7 @@ flux bootstrap github [flags]
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
       --timeout duration           timeout for this operation (default 5m0s)
+      --token-auth                 when enabled, the personal access token will be used instead of SSH deploy key
       --verbose                    print generated objects
   -v, --version string             toolkit version (default "latest")
       --watch-all-namespaces       watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
diff --git a/docs/cmd/flux_bootstrap_gitlab.md b/docs/cmd/flux_bootstrap_gitlab.md
index 131d7441..a4682511 100644
--- a/docs/cmd/flux_bootstrap_gitlab.md
+++ b/docs/cmd/flux_bootstrap_gitlab.md
@@ -21,22 +21,22 @@ flux bootstrap gitlab [flags]
   export GITLAB_TOKEN=<my-token>
 
   # Run bootstrap for a private repo using HTTPS token authentication 
-  flux bootstrap gitlab --owner=<group> --repository=<repo name>
+  flux bootstrap gitlab --owner=<group> --repository=<repo name> --token-auth
 
   # Run bootstrap for a private repo using SSH authentication
-  flux bootstrap gitlab --owner=<group> --repository=<repo name> --ssh-hostname=gitlab.com
+  flux bootstrap gitlab --owner=<group> --repository=<repo name>
 
   # Run bootstrap for a repository path
   flux bootstrap gitlab --owner=<group> --repository=<repo name> --path=dev-cluster
 
   # Run bootstrap for a public repository on a personal account
-  flux bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal=true
+  flux bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal --token-auth
 
   # Run bootstrap for a private repo hosted on a GitLab server 
-  flux bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain>
+  flux bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain> --token-auth
 
   # Run bootstrap for a an existing repository with a branch named main
-  flux bootstrap gitlab --owner=<organization> --repository=<repo name> --branch=main
+  flux bootstrap gitlab --owner=<organization> --repository=<repo name> --branch=main --token-auth
 
 ```
 
@@ -51,7 +51,7 @@ flux bootstrap gitlab [flags]
       --personal              is personal repository
       --private               is private repository (default true)
       --repository string     GitLab repository name
-      --ssh-hostname string   GitLab SSH hostname, when specified a deploy key will be added to the repository
+      --ssh-hostname string   GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one
 ```
 
 ### Options inherited from parent commands
@@ -68,6 +68,7 @@ flux bootstrap gitlab [flags]
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
       --timeout duration           timeout for this operation (default 5m0s)
+      --token-auth                 when enabled, the personal access token will be used instead of SSH deploy key
       --verbose                    print generated objects
   -v, --version string             toolkit version (default "latest")
       --watch-all-namespaces       watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
diff --git a/docs/guides/installation.md b/docs/guides/installation.md
index bfd34816..f8270a3a 100644
--- a/docs/guides/installation.md
+++ b/docs/guides/installation.md
@@ -129,6 +129,19 @@ To run the bootstrap for a repository hosted on GitHub Enterprise, you have to s
 
 ```sh
 flux bootstrap github \
+  --hostname=my-github-enterprise.com \
+  --ssh-hostname=my-github-enterprise.com \
+  --owner=my-github-organization \
+  --repository=my-repository \
+  --branch=main \
+  --path=clusters/my-cluster
+```
+
+If your GitHub Enterprise has SSH access disabled, you can use HTTPS and token authentication with:
+
+```sh
+flux bootstrap github \
+  --token-auth \
   --hostname=my-github-enterprise.com \
   --owner=my-github-organization \
   --repository=my-repository \
@@ -155,6 +168,7 @@ flux bootstrap gitlab \
   --repository=my-repository \
   --branch=master \
   --path=clusters/my-cluster \
+  --token-auth \
   --personal
 ```
 
@@ -189,6 +203,7 @@ To run the bootstrap for a repository hosted on GitLab on-prem or enterprise, yo
 ```sh
 flux bootstrap gitlab \
   --hostname=my-gitlab.com \
+  --token-auth \
   --owner=my-gitlab-group \
   --repository=my-repository \
   --branch=master \
-- 
GitLab