Skip to content

Commit

Permalink
Implement --deploy-token-auth in GitLab bootstrapping
Browse files Browse the repository at this point in the history
This change set implements support for the `--deploy-token-auth` option
in the `flux bootstrap gitlab` command.

That option will reconcile a GitLab Project Deploy Token to use for the
authentication of the GitLab git repository.
A GitLab Project Deploy Token can be used the same way as a Personal
Access Token which is already supported via `--token-auth`.
The difference with the GitLab Project Deploy Token is that the token is
managed (created, updated, deleted) by Flux and not provided by the
user.

This change is transparent to the source-controller.

A prerequisite for this change is the
`fluxcd/go-git-providers` change here:

* fluxcd/go-git-providers#191

See related discussion here: fluxcd#3595
GitLab Issue here: https://gitlab.com/gitlab-org/gitlab/-/issues/392605

Signed-off-by: Timo Furrer <[email protected]>
  • Loading branch information
timofurrer authored and souleb committed Jul 10, 2023
1 parent 96b9da1 commit 8a50f4e
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 12 deletions.
40 changes: 28 additions & 12 deletions cmd/flux/bootstrap_gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ the bootstrap command will perform an upgrade if needed.`,
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for a an existing repository with a branch named main
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth`,
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth
# Run bootstrap for a private repository using Deploy Token authentication
flux bootstrap gitlab --owner=<group> --repository=<repository name> --deploy-token-auth
`,
RunE: bootstrapGitLabCmdRun,
}

Expand All @@ -77,16 +81,17 @@ const (
)

type gitlabFlags struct {
owner string
repository string
interval time.Duration
personal bool
private bool
hostname string
path flags.SafeRelativePath
teams []string
readWriteKey bool
reconcile bool
owner string
repository string
interval time.Duration
personal bool
private bool
hostname string
path flags.SafeRelativePath
teams []string
readWriteKey bool
reconcile bool
deployTokenAuth bool
}

var gitlabArgs gitlabFlags
Expand All @@ -102,6 +107,7 @@ func init() {
bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.deployTokenAuth, "deploy-token-auth", false, "when enabled, a Project Deploy Token is generated and will be used instead of the SSH deploy token")

bootstrapCmd.AddCommand(bootstrapGitLabCmd)
}
Expand All @@ -123,6 +129,10 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return err
}

if bootstrapArgs.tokenAuth && gitlabArgs.deployTokenAuth {
return fmt.Errorf("--token-auth and --deploy-token-auth cannot be set both.")
}

if err := bootstrapValidate(); err != nil {
return err
}
Expand Down Expand Up @@ -225,6 +235,9 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Username = "git"
secretOpts.Password = glToken
secretOpts.CAFile = caBundle
} else if gitlabArgs.deployTokenAuth {
// the actual deploy token will be reconciled later
secretOpts.CAFile = caBundle
} else {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil {
Expand Down Expand Up @@ -274,9 +287,12 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
}
if bootstrapArgs.tokenAuth {
if bootstrapArgs.tokenAuth || gitlabArgs.deployTokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
}
if gitlabArgs.deployTokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth())
}
if !gitlabArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
}
Expand Down
69 changes: 69 additions & 0 deletions pkg/bootstrap/bootstrap_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type GitProviderBootstrapper struct {

sshHostname string

useDeployTokenAuth bool

provider gitprovider.Client
}

Expand Down Expand Up @@ -184,6 +186,16 @@ func (o reconcileOption) applyGitProvider(b *GitProviderBootstrapper) {
b.reconcile = true
}

func WithDeployTokenAuth() GitProviderOption {
return deployTokenAuthOption(true)
}

type deployTokenAuthOption bool

func (o deployTokenAuthOption) applyGitProvider(b *GitProviderBootstrapper) {
b.useDeployTokenAuth = true
}

func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options) error {
if b.repository == nil {
return errors.New("repository is required")
Expand All @@ -208,6 +220,26 @@ func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, optio
return b.PlainGitBootstrapper.ReconcileSyncConfig(ctx, options)
}

func (b *GitProviderBootstrapper) ReconcileSourceSecret(ctx context.Context, options sourcesecret.Options) error {
if b.repository == nil {
return errors.New("repository is required")
}

if b.useDeployTokenAuth {
deployTokenInfo, err := b.reconcileDeployToken(ctx, options)
if err != nil {
return err
}

if deployTokenInfo != nil {
options.Username = deployTokenInfo.Username
options.Password = deployTokenInfo.Token
}
}

return b.PlainGitBootstrapper.ReconcileSourceSecret(ctx, options)
}

// ReconcileRepository reconciles an organization or user repository with the
// GitProviderBootstrapper configuration. On success, the URL in the embedded
// PlainGitBootstrapper is set to clone URL for the configured protocol.
Expand Down Expand Up @@ -261,6 +293,32 @@ func (b *GitProviderBootstrapper) reconcileDeployKey(ctx context.Context, secret
return nil
}

func (b *GitProviderBootstrapper) reconcileDeployToken(ctx context.Context, options sourcesecret.Options) (*gitprovider.DeployTokenInfo, error) {
dts, err := b.repository.DeployTokens()
if err != nil {
return nil, err
}

b.logger.Actionf("checking to reconcile deploy token for source secret")
name := deployTokenName(options.Namespace, b.branch, options.Name, options.TargetPath)
deployTokenInfo := gitprovider.DeployTokenInfo{Name: name}

deployToken, changed, err := dts.Reconcile(ctx, deployTokenInfo)
if err != nil {
return nil, err
}

if changed {
b.logger.Successf("configured deploy token %q for %q", deployTokenInfo.Name, b.repository.Repository().String())
deployTokenInfo := deployToken.Get()
return &deployTokenInfo, nil
}

b.logger.Successf("reconciled deploy token for source secret")

return nil, nil
}

// reconcileOrgRepository reconciles a gitprovider.OrgRepository
// with the GitProviderBootstrapper values, including any
// gitprovider.TeamAccessInfo configurations.
Expand Down Expand Up @@ -554,6 +612,17 @@ func deployKeyName(namespace, secretName, branch, path string) string {
return name
}

func deployTokenName(namespace, secretName, branch, path string) string {
var elems []string
for _, v := range []string{namespace, secretName, branch, path} {
if v == "" {
continue
}
elems = append(elems, v)
}
return strings.Join(elems, "-")
}

// setHostname is a helper to replace the hostname of the given URL.
// TODO(hidde): support for this should be added in go-git-providers.
func setHostname(URL, hostname string) (string, error) {
Expand Down

0 comments on commit 8a50f4e

Please sign in to comment.