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
# 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,
}
@ -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
@ -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)
}
@ -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
}
@ -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 {
@ -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"))
}

@ -59,6 +59,8 @@ type GitProviderBootstrapper struct {
sshHostname string
useDeployTokenAuth bool
provider gitprovider.Client
}
@ -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")
@ -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.
@ -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.
@ -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) {

Loading…
Cancel
Save