Implement `--deploy-token-auth` in GitLab bootstrapping

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:

* https://github.com/fluxcd/go-git-providers/pull/191

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

Signed-off-by: Timo Furrer <tuxtimo@gmail.com>
pull/3654/head
Timo Furrer 2 years ago committed by Hidde Beydals
parent 91d1e1df48
commit 2e1721ca85

@ -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 flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for a an existing repository with a branch named main # 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, RunE: bootstrapGitLabCmdRun,
} }
@ -77,16 +81,17 @@ const (
) )
type gitlabFlags struct { type gitlabFlags struct {
owner string owner string
repository string repository string
interval time.Duration interval time.Duration
personal bool personal bool
private bool private bool
hostname string hostname string
path flags.SafeRelativePath path flags.SafeRelativePath
teams []string teams []string
readWriteKey bool readWriteKey bool
reconcile bool reconcile bool
deployTokenAuth bool
} }
var gitlabArgs gitlabFlags var gitlabArgs gitlabFlags
@ -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().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.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.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) bootstrapCmd.AddCommand(bootstrapGitLabCmd)
} }
@ -123,6 +129,10 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return err 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 { if err := bootstrapValidate(); err != nil {
return err return err
} }
@ -225,6 +235,9 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Username = "git" secretOpts.Username = "git"
secretOpts.Password = glToken secretOpts.Password = glToken
secretOpts.CAFile = caBundle secretOpts.CAFile = caBundle
} else if gitlabArgs.deployTokenAuth {
// the actual deploy token will be reconciled later
secretOpts.CAFile = caBundle
} else { } else {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password) keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil { if err != nil {
@ -274,9 +287,12 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
} }
if bootstrapArgs.tokenAuth { if bootstrapArgs.tokenAuth || gitlabArgs.deployTokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https")) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
} }
if gitlabArgs.deployTokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth())
}
if !gitlabArgs.private { if !gitlabArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public")) bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
} }

@ -59,6 +59,8 @@ type GitProviderBootstrapper struct {
sshHostname string sshHostname string
useDeployTokenAuth bool
provider gitprovider.Client provider gitprovider.Client
} }
@ -184,6 +186,16 @@ func (o reconcileOption) applyGitProvider(b *GitProviderBootstrapper) {
b.reconcile = true 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 { func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options) error {
if b.repository == nil { if b.repository == nil {
return errors.New("repository is required") return errors.New("repository is required")
@ -208,6 +220,26 @@ func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, optio
return b.PlainGitBootstrapper.ReconcileSyncConfig(ctx, options) 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 // ReconcileRepository reconciles an organization or user repository with the
// GitProviderBootstrapper configuration. On success, the URL in the embedded // GitProviderBootstrapper configuration. On success, the URL in the embedded
// PlainGitBootstrapper is set to clone URL for the configured protocol. // PlainGitBootstrapper is set to clone URL for the configured protocol.
@ -261,6 +293,32 @@ func (b *GitProviderBootstrapper) reconcileDeployKey(ctx context.Context, secret
return nil 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 // reconcileOrgRepository reconciles a gitprovider.OrgRepository
// with the GitProviderBootstrapper values, including any // with the GitProviderBootstrapper values, including any
// gitprovider.TeamAccessInfo configurations. // gitprovider.TeamAccessInfo configurations.
@ -554,6 +612,17 @@ func deployKeyName(namespace, secretName, branch, path string) string {
return name 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. // setHostname is a helper to replace the hostname of the given URL.
// TODO(hidde): support for this should be added in go-git-providers. // TODO(hidde): support for this should be added in go-git-providers.
func setHostname(URL, hostname string) (string, error) { func setHostname(URL, hostname string) (string, error) {

Loading…
Cancel
Save