diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index e5d27229..34d877a0 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -65,7 +65,11 @@ the bootstrap command will perform an upgrade if needed.`, flux bootstrap gitlab --owner= --repository= --hostname= --token-auth # Run bootstrap for a an existing repository with a branch named main - flux bootstrap gitlab --owner= --repository= --branch=main --token-auth`, + flux bootstrap gitlab --owner= --repository= --branch=main --token-auth + + # Run bootstrap for a private repository using Deploy Token authentication + flux bootstrap gitlab --owner= --repository= --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")) } diff --git a/go.mod b/go.mod index 275974fc..5df8c924 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 github.com/cyphar/filepath-securejoin v0.2.3 github.com/distribution/distribution/v3 v3.0.0-20230223072852-e5d5810851d1 - github.com/fluxcd/go-git-providers v0.14.0 + github.com/fluxcd/go-git-providers v0.15.0 github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4 github.com/fluxcd/helm-controller/api v0.31.2 github.com/fluxcd/image-automation-controller/api v0.31.0 @@ -125,7 +125,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/gomodule/redigo v1.8.2 // indirect github.com/gonvenience/neat v1.3.11 // indirect github.com/gonvenience/term v1.0.2 // indirect @@ -190,7 +190,7 @@ require ( github.com/texttheater/golang-levenshtein v1.0.1 // indirect github.com/vbatts/tar-split v0.11.2 // indirect github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect - github.com/xanzy/go-gitlab v0.78.0 // indirect + github.com/xanzy/go-gitlab v0.81.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xlab/treeprint v1.1.0 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect @@ -199,7 +199,7 @@ require ( go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.3.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect @@ -207,7 +207,7 @@ require ( golang.org/x/tools v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.29.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index c6f75694..4acc2861 100644 --- a/go.sum +++ b/go.sum @@ -195,8 +195,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= -github.com/fluxcd/go-git-providers v0.14.0 h1:F662wkiMevhGYKCwe9AJItpq42aJbd+tQq7d5Yskh/k= -github.com/fluxcd/go-git-providers v0.14.0/go.mod h1:cxVVQEBEswAIgbRVi50Cf6pEM/+RhNcNwDpsIkN6pG0= +github.com/fluxcd/go-git-providers v0.15.0 h1:WuBw+CcmXi7UhSf8mFNB6tbGelS0kVlgI9wtlWjzimk= +github.com/fluxcd/go-git-providers v0.15.0/go.mod h1:SgShGfc2rA5Gi7N65CBjMOIolarDZzZCMzEHOoY3P0I= github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4 h1:Gm5sGGk+/Wq6RhX4xpCZ2IqjDp5XkjlhENaAuAlpdKc= github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= github.com/fluxcd/helm-controller/api v0.31.2 h1:d/lbCPYiQP+YnPNhdIOGRzHUWDIhnxI9dUSaOCx/RK8= @@ -319,8 +319,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/gonvenience/bunt v1.3.4 h1:Row599Ohja2BPooaqd1tHYdTAKu6SWq7W/UeakTXddM= @@ -611,8 +612,8 @@ github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlI github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= -github.com/xanzy/go-gitlab v0.78.0 h1:8jUHfQVAprG04Av5g0PxVd3CNsZ5hCbojIax7Hba1mE= -github.com/xanzy/go-gitlab v0.78.0/go.mod h1:DlByVTSXhPsJMYL6+cm8e8fTJjeBmhrXdC/yvkKKt6M= +github.com/xanzy/go-gitlab v0.81.0 h1:ofbhZ5ZY9AjHATWQie4qd2JfncdUmvcSA/zfQB767Dk= +github.com/xanzy/go-gitlab v0.81.0/go.mod h1:VMbY3JIWdZ/ckvHbQqkyd3iYk2aViKrNIQ23IbFMQDo= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -743,8 +744,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= -golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -979,8 +980,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= +google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/bootstrap/bootstrap_provider.go b/pkg/bootstrap/bootstrap_provider.go index 63600926..74aa48ef 100644 --- a/pkg/bootstrap/bootstrap_provider.go +++ b/pkg/bootstrap/bootstrap_provider.go @@ -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) {