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 committed Mar 6, 2023
1 parent 2e4de67 commit 6367bf7
Show file tree
Hide file tree
Showing 2 changed files with 108 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
80 changes: 80 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 := b.repository.DeployTokens()
if dts == nil {
return nil, errors.New("wanted to reconcile deploy token, but it's not supported by this bootstrap provider")
}

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

deployToken, changed, err := b.repository.DeployTokens().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,28 @@ func deployKeyName(namespace, secretName, branch, path string) string {
return name
}

// newDeployTokenInfo constructs a gitprovider.DeployTokenInfo with the
// given values and returns the result.
func newDeployTokenInfo(name string) gitprovider.DeployTokenInfo {
tokenInfo := gitprovider.DeployTokenInfo{Name: name}
return tokenInfo
}

func deployTokenName(namespace, secretName, branch, path string) string {
var name string
for _, v := range []string{namespace, secretName, branch, path} {
if v == "" {
continue
}
if name == "" {
name = v
} else {
name = name + "-" + v
}
}
return name
}

// 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 6367bf7

Please sign in to comment.