feat: add bearer token auth (#21462)

Signed-off-by: reggie-k <regina.voloshin@codefresh.io>
Co-authored-by: Pasha Kostohrys <pasha.kostohrys@gmail.com>
This commit is contained in:
Regina Voloshin
2025-02-24 12:44:07 +02:00
committed by GitHub
parent 33ad0a7ba7
commit 8044d68492
31 changed files with 1585 additions and 854 deletions

20
assets/swagger.json generated
View File

@@ -3777,6 +3777,12 @@
"description": "Whether to use azure workload identity for authentication.",
"name": "useAzureWorkloadIdentity",
"in": "query"
},
{
"type": "string",
"description": "BearerToken contains the bearer token used for Git auth at the repo server.",
"name": "bearerToken",
"in": "query"
}
],
"responses": {
@@ -4609,6 +4615,12 @@
"description": "Whether to use azure workload identity for authentication.",
"name": "useAzureWorkloadIdentity",
"in": "query"
},
{
"type": "string",
"description": "BearerToken contains the bearer token used for Git auth at the repo server.",
"name": "bearerToken",
"in": "query"
}
],
"responses": {
@@ -8912,6 +8924,10 @@
"type": "object",
"title": "RepoCreds holds the definition for repository credentials",
"properties": {
"bearerToken": {
"type": "string",
"title": "BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server"
},
"enableOCI": {
"type": "boolean",
"title": "EnableOCI specifies whether helm-oci support should be enabled for this repo"
@@ -9003,6 +9019,10 @@
"type": "object",
"title": "Repository is a repository holding application configurations",
"properties": {
"bearerToken": {
"type": "string",
"title": "BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server"
},
"connectionState": {
"$ref": "#/definitions/v1alpha1ConnectionState"
},

View File

@@ -54,6 +54,9 @@ func NewGenRepoSpecCommand() *cobra.Command {
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
argocd admin repo generate-spec https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
# Add a private Git BitBucket Data Center repository via HTTPS using bearer token:
argocd admin repo generate-spec https://bitbucket.example.com/scm/proj/repo --bearer-token secret-token
# Add a private Git repository via HTTPS using username/password without verifying the server's TLS certificate
argocd admin repo generate-spec https://git.example.com/repos/repo --username git --password secret --insecure-skip-server-verification
@@ -138,6 +141,13 @@ func NewGenRepoSpecCommand() *cobra.Command {
repoOpts.Repo.Password = cli.PromptPassword(repoOpts.Repo.Password)
}
err := cmdutil.ValidateBearerTokenAndPasswordCombo(repoOpts.Repo.BearerToken, repoOpts.Repo.Password)
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForHTTPSRepoOnly(repoOpts.Repo.BearerToken, git.IsHTTPSURL(repoOpts.Repo.Repo))
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForGitOnly(repoOpts.Repo.BearerToken, repoOpts.Repo.Type)
errors.CheckError(err)
argoCDCM := &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
@@ -155,7 +165,7 @@ func NewGenRepoSpecCommand() *cobra.Command {
settingsMgr := settings.NewSettingsManager(ctx, kubeClientset, ArgoCDNamespace)
argoDB := db.NewDB(ArgoCDNamespace, settingsMgr, kubeClientset)
_, err := argoDB.CreateRepository(ctx, &repoOpts.Repo)
_, err = argoDB.CreateRepository(ctx, &repoOpts.Repo)
errors.CheckError(err)
secret, err := kubeClientset.CoreV1().Secrets(ArgoCDNamespace).Get(ctx, db.RepoURLToSecretName(repoSecretPrefix, repoOpts.Repo.Repo, repoOpts.Repo.Project), metav1.GetOptions{})

View File

@@ -197,6 +197,13 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
repoOpts.Repo.Password = cli.PromptPassword(repoOpts.Repo.Password)
}
err := cmdutil.ValidateBearerTokenAndPasswordCombo(repoOpts.Repo.BearerToken, repoOpts.Repo.Password)
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForGitOnly(repoOpts.Repo.BearerToken, repoOpts.Repo.Type)
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForHTTPSRepoOnly(repoOpts.Repo.BearerToken, git.IsHTTPSURL(repoOpts.Repo.Repo))
errors.CheckError(err)
// We let the server check access to the repository before adding it. If
// it is a private repo, but we cannot access with with the credentials
// that were supplied, we bail out.
@@ -210,6 +217,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
Name: repoOpts.Repo.Name,
Username: repoOpts.Repo.Username,
Password: repoOpts.Repo.Password,
BearerToken: repoOpts.Repo.BearerToken,
SshPrivateKey: repoOpts.Repo.SSHPrivateKey,
TlsClientCertData: repoOpts.Repo.TLSClientCertData,
TlsClientCertKey: repoOpts.Repo.TLSClientCertKey,
@@ -225,7 +233,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
ForceHttpBasicAuth: repoOpts.Repo.ForceHttpBasicAuth,
UseAzureWorkloadIdentity: repoOpts.Repo.UseAzureWorkloadIdentity,
}
_, err := repoIf.ValidateAccess(ctx, &repoAccessReq)
_, err = repoIf.ValidateAccess(ctx, &repoAccessReq)
errors.CheckError(err)
repoCreateReq := repositorypkg.RepoCreateRequest{

View File

@@ -11,6 +11,7 @@ import (
"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
cmdutil "github.com/argoproj/argo-cd/v3/cmd/util"
"github.com/argoproj/argo-cd/v3/common"
argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
repocredspkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/repocreds"
@@ -65,6 +66,9 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
repocredsAddExamples := ` # Add credentials with user/pass authentication to use for all repositories under https://git.example.com/repos
argocd repocreds add https://git.example.com/repos/ --username git --password secret
# Add credentials with bearer token authentication to use for all BitBucket Data Center repositories under https://bitbucket.example.com/scm
argocd repocreds add https://bitbucket.example.com/scm/ --bearer-token secret-token
# Add credentials with SSH private key authentication to use for all repositories under ssh://git@git.example.com/repos
argocd repocreds add ssh://git@git.example.com/repos/ --ssh-private-key-path ~/.ssh/id_rsa
@@ -165,6 +169,13 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
repo.Password = cli.PromptPassword(repo.Password)
}
err := cmdutil.ValidateBearerTokenAndPasswordCombo(repo.BearerToken, repo.Password)
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForGitOnly(repo.BearerToken, repo.Type)
errors.CheckError(err)
err = cmdutil.ValidateBearerTokenForHTTPSRepoOnly(repo.BearerToken, git.IsHTTPSURL(repo.URL))
errors.CheckError(err)
repoCreateReq := repocredspkg.RepoCredsCreateRequest{
Creds: &repo,
Upsert: upsert,
@@ -177,6 +188,7 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
}
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&repo.BearerToken, "bearer-token", "", "bearer token to the Git repository")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")

View File

@@ -1,6 +1,39 @@
package util
import (
stderrors "errors"
)
var (
LogFormat string
LogLevel string
)
func ValidateBearerTokenForHTTPSRepoOnly(bearerToken string, isHTTPS bool) error {
// Bearer token is only valid for HTTPS repositories
if bearerToken != "" {
if !isHTTPS {
err := stderrors.New("--bearer-token is only supported for HTTPS repositories")
return err
}
}
return nil
}
func ValidateBearerTokenForGitOnly(bearerToken string, repoType string) error {
// Bearer token is only valid for Git repositories
if bearerToken != "" && repoType != "git" {
err := stderrors.New("--bearer-token is only supported for Git repositories")
return err
}
return nil
}
func ValidateBearerTokenAndPasswordCombo(bearerToken string, password string) error {
// Either the password or the bearer token must be set, but not both
if bearerToken != "" && password != "" {
err := stderrors.New("only --bearer-token or --password is allowed, not both")
return err
}
return nil
}

155
cmd/util/common_test.go Normal file
View File

@@ -0,0 +1,155 @@
package util
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestValidateBearerTokenAndPasswordCombo(t *testing.T) {
tests := []struct {
name string
bearerToken string
password string
expectError bool
errorMsg string
}{
{
name: "Both token and password set",
bearerToken: "some-token",
password: "some-password",
expectError: true,
errorMsg: "only --bearer-token or --password is allowed, not both",
},
{
name: "Only token set",
bearerToken: "some-token",
password: "",
expectError: false,
},
{
name: "Only password set",
bearerToken: "",
password: "some-password",
expectError: false,
},
{
name: "Neither token nor password set",
bearerToken: "",
password: "",
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateBearerTokenAndPasswordCombo(tt.bearerToken, tt.password)
if tt.expectError {
require.ErrorContains(t, err, tt.errorMsg)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateBearerTokenForGitOnly(t *testing.T) {
tests := []struct {
name string
bearerToken string
repoType string
expectError bool
errorMsg string
}{
{
name: "Bearer token with helm repo",
bearerToken: "some-token",
repoType: "helm",
expectError: true,
errorMsg: "--bearer-token is only supported for Git repositories",
},
{
name: "Bearer token with git repo",
bearerToken: "some-token",
repoType: "git",
expectError: false,
},
{
name: "No bearer token with helm repo",
bearerToken: "",
repoType: "helm",
expectError: false,
},
{
name: "No bearer token with git repo",
bearerToken: "",
repoType: "git",
expectError: false,
},
{
name: "Bearer token with empty repo",
bearerToken: "some-token",
repoType: "",
expectError: true,
errorMsg: "--bearer-token is only supported for Git repositories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateBearerTokenForGitOnly(tt.bearerToken, tt.repoType)
if tt.expectError {
require.ErrorContains(t, err, tt.errorMsg)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateBearerTokenForHTTPSRepoOnly(t *testing.T) {
tests := []struct {
name string
bearerToken string
isHTTPS bool
expectError bool
errorMsg string
}{
{
name: "Bearer token with HTTPS repo",
bearerToken: "some-token",
isHTTPS: true,
expectError: false,
},
{
name: "Bearer token with non-HTTPS repo",
bearerToken: "some-token",
isHTTPS: false,
expectError: true,
errorMsg: "--bearer-token is only supported for HTTPS repositories",
},
{
name: "No bearer token with HTTPS repo",
bearerToken: "",
isHTTPS: true,
expectError: false,
},
{
name: "No bearer token with non-HTTPS repo",
bearerToken: "",
isHTTPS: false,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateBearerTokenForHTTPSRepoOnly(tt.bearerToken, tt.isHTTPS)
if tt.expectError {
require.ErrorContains(t, err, tt.errorMsg)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -34,6 +34,7 @@ func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
command.Flags().StringVar(&opts.Repo.Project, "project", "", "project of the repository")
command.Flags().StringVar(&opts.Repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&opts.Repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&opts.Repo.BearerToken, "bearer-token", "", "bearer token to the Git BitBucket Data Center repository")
command.Flags().StringVar(&opts.SshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().StringVar(&opts.TlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
command.Flags().StringVar(&opts.TlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")

View File

@@ -21,6 +21,9 @@ argocd admin repo generate-spec REPOURL [flags]
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
argocd admin repo generate-spec https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
# Add a private Git BitBucket Data Center repository via HTTPS using bearer token:
argocd admin repo generate-spec https://bitbucket.example.com/scm/proj/repo --bearer-token secret-token
# Add a private Git repository via HTTPS using username/password without verifying the server's TLS certificate
argocd admin repo generate-spec https://git.example.com/repos/repo --username git --password secret --insecure-skip-server-verification
@@ -38,6 +41,7 @@ argocd admin repo generate-spec REPOURL [flags]
### Options
```
--bearer-token string bearer token to the Git BitBucket Data Center repository
--enable-lfs enable git-lfs (Large File Support) on this repository
--enable-oci enable helm-oci (Helm OCI-Based Repository)
--force-http-basic-auth whether to force use of basic auth when connecting repository via HTTP

View File

@@ -52,6 +52,7 @@ argocd repo add REPOURL [flags]
### Options
```
--bearer-token string bearer token to the Git BitBucket Data Center repository
--enable-lfs enable git-lfs (Large File Support) on this repository
--enable-oci enable helm-oci (Helm OCI-Based Repository)
--force-http-basic-auth whether to force use of basic auth when connecting repository via HTTP

View File

@@ -14,6 +14,9 @@ argocd repocreds add REPOURL [flags]
# Add credentials with user/pass authentication to use for all repositories under https://git.example.com/repos
argocd repocreds add https://git.example.com/repos/ --username git --password secret
# Add credentials with bearer token authentication to use for all BitBucket Data Center repositories under https://bitbucket.example.com/scm
argocd repocreds add https://bitbucket.example.com/scm/ --bearer-token secret-token
# Add credentials with SSH private key authentication to use for all repositories under ssh://git@git.example.com/repos
argocd repocreds add ssh://git@git.example.com/repos/ --ssh-private-key-path ~/.ssh/id_rsa
@@ -34,6 +37,7 @@ argocd repocreds add REPOURL [flags]
### Options
```
--bearer-token string bearer token to the Git repository
--enable-oci Specifies whether helm-oci support should be enabled for this repo
--force-http-basic-auth whether to force basic auth when connecting via HTTP
--gcp-service-account-key-path string service account key for the Google Cloud Platform

View File

@@ -397,10 +397,12 @@ type RepoAccessQuery struct {
// Whether to force HTTP basic auth
ForceHttpBasicAuth bool `protobuf:"varint,19,opt,name=forceHttpBasicAuth,proto3" json:"forceHttpBasicAuth,omitempty"`
// Whether to use azure workload identity for authentication
UseAzureWorkloadIdentity bool `protobuf:"varint,20,opt,name=useAzureWorkloadIdentity,proto3" json:"useAzureWorkloadIdentity,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
UseAzureWorkloadIdentity bool `protobuf:"varint,20,opt,name=useAzureWorkloadIdentity,proto3" json:"useAzureWorkloadIdentity,omitempty"`
// BearerToken contains the bearer token used for Git auth at the repo server
BearerToken string `protobuf:"bytes,21,opt,name=bearerToken,proto3" json:"bearerToken,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RepoAccessQuery) Reset() { *m = RepoAccessQuery{} }
@@ -569,6 +571,13 @@ func (m *RepoAccessQuery) GetUseAzureWorkloadIdentity() bool {
return false
}
func (m *RepoAccessQuery) GetBearerToken() string {
if m != nil {
return m.BearerToken
}
return ""
}
type RepoResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@@ -739,91 +748,92 @@ func init() {
}
var fileDescriptor_8d38260443475705 = []byte{
// 1335 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x98, 0x5d, 0x6f, 0x1b, 0x45,
0x17, 0xc7, 0xb5, 0x49, 0xe3, 0x26, 0x27, 0x4d, 0xeb, 0x4e, 0x92, 0x3e, 0xfb, 0xb8, 0x69, 0x1a,
0xb6, 0xa5, 0x4a, 0xa3, 0x76, 0xdd, 0xb8, 0x20, 0xaa, 0x22, 0x90, 0xdc, 0xa4, 0x6a, 0x23, 0x22,
0x5a, 0xb6, 0x2a, 0x95, 0x10, 0x08, 0x4d, 0xd7, 0x27, 0xf6, 0xb6, 0x9b, 0xdd, 0xe9, 0xcc, 0xd8,
0xad, 0xa9, 0x7a, 0xc3, 0x05, 0x42, 0x82, 0x1b, 0x84, 0x40, 0x5c, 0x01, 0x17, 0x48, 0x48, 0x70,
0xcf, 0x67, 0xe0, 0x12, 0x89, 0x6b, 0x24, 0x54, 0xf1, 0x21, 0xb8, 0x44, 0x33, 0xb3, 0xde, 0x5d,
0x3b, 0x7e, 0x49, 0xd5, 0x24, 0x77, 0x33, 0xe7, 0xcc, 0x9e, 0xf3, 0x9b, 0xff, 0x9c, 0x79, 0xb1,
0xc1, 0x11, 0xc8, 0x5b, 0xc8, 0xcb, 0x1c, 0x59, 0x2c, 0x02, 0x19, 0xf3, 0x76, 0xae, 0xe9, 0x32,
0x1e, 0xcb, 0x98, 0x40, 0x66, 0x29, 0x2d, 0xd4, 0xe3, 0xb8, 0x1e, 0x62, 0x99, 0xb2, 0xa0, 0x4c,
0xa3, 0x28, 0x96, 0x54, 0x06, 0x71, 0x24, 0xcc, 0xc8, 0xd2, 0x66, 0x3d, 0x90, 0x8d, 0xe6, 0x7d,
0xd7, 0x8f, 0xb7, 0xcb, 0x94, 0xd7, 0x63, 0xc6, 0xe3, 0x07, 0xba, 0x71, 0xd1, 0xaf, 0x95, 0x5b,
0x97, 0xcb, 0xec, 0x61, 0x5d, 0x7d, 0x29, 0xca, 0x94, 0xb1, 0x30, 0xf0, 0xf5, 0xb7, 0xe5, 0xd6,
0x2a, 0x0d, 0x59, 0x83, 0xae, 0x96, 0xeb, 0x18, 0x21, 0xa7, 0x12, 0x6b, 0x49, 0xb4, 0xeb, 0x23,
0xa2, 0x69, 0xac, 0x91, 0xf8, 0x4e, 0x1b, 0x66, 0x3c, 0x64, 0x71, 0x95, 0x31, 0xf1, 0x5e, 0x13,
0x79, 0x9b, 0x10, 0x38, 0xa4, 0x06, 0xd9, 0xd6, 0x92, 0xb5, 0x3c, 0xe5, 0xe9, 0x36, 0x29, 0xc1,
0x24, 0xc7, 0x56, 0x20, 0x82, 0x38, 0xb2, 0xc7, 0xb4, 0x3d, 0xed, 0x13, 0x1b, 0x0e, 0x53, 0xc6,
0xde, 0xa5, 0xdb, 0x68, 0x8f, 0x6b, 0x57, 0xa7, 0x4b, 0x16, 0x01, 0x28, 0x63, 0xb7, 0x79, 0xfc,
0x00, 0x7d, 0x69, 0x1f, 0xd2, 0xce, 0x9c, 0xc5, 0x59, 0x85, 0xc3, 0x55, 0xc6, 0x36, 0xa2, 0xad,
0x58, 0x25, 0x95, 0x6d, 0x86, 0x9d, 0xa4, 0xaa, 0xad, 0x6c, 0x8c, 0xca, 0x46, 0x92, 0x50, 0xb7,
0x9d, 0x7f, 0x2d, 0x98, 0x4d, 0x70, 0xd7, 0x51, 0xd2, 0x20, 0x4c, 0xa0, 0xeb, 0x50, 0x10, 0x71,
0x93, 0xfb, 0x26, 0xc2, 0x74, 0xe5, 0x96, 0x9b, 0xa9, 0xe3, 0x76, 0xd4, 0xd1, 0x8d, 0x8f, 0xfd,
0x9a, 0xdb, 0xba, 0xec, 0xb2, 0x87, 0x75, 0x57, 0x69, 0xed, 0xe6, 0xb4, 0x76, 0x3b, 0x5a, 0xbb,
0xd5, 0xcc, 0x78, 0x47, 0x87, 0xf5, 0x92, 0xf0, 0xf9, 0xd9, 0x8e, 0x0d, 0x9b, 0xed, 0x78, 0xef,
0x6c, 0xc9, 0x12, 0x4c, 0x9b, 0x18, 0x1b, 0x51, 0x0d, 0x9f, 0x68, 0x39, 0x26, 0xbc, 0xbc, 0x89,
0x2c, 0xc0, 0x54, 0x0b, 0xb9, 0x12, 0x75, 0xa3, 0x66, 0x4f, 0x68, 0x7f, 0x66, 0x70, 0xde, 0x82,
0x62, 0x67, 0xa1, 0x3c, 0x14, 0x2c, 0x8e, 0x04, 0x92, 0xf3, 0x30, 0x11, 0x48, 0xdc, 0x16, 0xb6,
0xb5, 0x34, 0xbe, 0x3c, 0x5d, 0x99, 0x75, 0x73, 0xcb, 0x9b, 0x48, 0xeb, 0x99, 0x11, 0x8e, 0x0f,
0x53, 0xea, 0xf3, 0xc1, 0x6b, 0xec, 0xc0, 0x91, 0xad, 0x58, 0x4d, 0x15, 0xb7, 0x38, 0x0a, 0x23,
0xfb, 0xa4, 0xd7, 0x65, 0x1b, 0x35, 0x47, 0xe7, 0xaf, 0x09, 0x38, 0xa6, 0x21, 0x7d, 0x1f, 0xc5,
0xf0, 0x7a, 0x6a, 0x0a, 0xe4, 0x51, 0x26, 0x63, 0xda, 0x57, 0x3e, 0x46, 0x85, 0x78, 0x1c, 0xf3,
0x5a, 0x92, 0x21, 0xed, 0x93, 0xb3, 0x30, 0x23, 0x44, 0xe3, 0x36, 0x0f, 0x5a, 0x54, 0xe2, 0x3b,
0xd8, 0x4e, 0x8a, 0xaa, 0xdb, 0xa8, 0x22, 0x04, 0x91, 0x40, 0xbf, 0xc9, 0x51, 0xcb, 0x38, 0xe9,
0xa5, 0x7d, 0x72, 0x01, 0x8e, 0xcb, 0x50, 0xac, 0x85, 0x01, 0x46, 0x72, 0x0d, 0xb9, 0x5c, 0xa7,
0x92, 0xda, 0x05, 0x1d, 0x65, 0xa7, 0x83, 0xac, 0x40, 0xb1, 0xcb, 0xa8, 0x52, 0x1e, 0xd6, 0x83,
0x77, 0xd8, 0xd3, 0x12, 0x9e, 0xea, 0x2e, 0x61, 0x3d, 0x47, 0x30, 0x36, 0x3d, 0xbf, 0x05, 0x98,
0xc2, 0x88, 0xde, 0x0f, 0xf1, 0x96, 0x1f, 0xd8, 0xd3, 0x1a, 0x2f, 0x33, 0x90, 0x4b, 0x30, 0x6b,
0x2a, 0xb7, 0xaa, 0x54, 0x4d, 0xe7, 0x79, 0x44, 0x07, 0xe8, 0xe7, 0x52, 0x75, 0x95, 0x9a, 0x37,
0xd6, 0xed, 0x99, 0x25, 0x6b, 0x79, 0xdc, 0xcb, 0x9b, 0xc8, 0x15, 0xf8, 0x5f, 0xd6, 0x8d, 0x84,
0xa4, 0x61, 0xa8, 0x4b, 0x7b, 0x63, 0xdd, 0x3e, 0xaa, 0x47, 0x0f, 0x72, 0x93, 0xb7, 0xa1, 0x94,
0xba, 0xae, 0x47, 0x12, 0x39, 0xe3, 0x81, 0xc0, 0x6b, 0x54, 0xe0, 0x5d, 0x1e, 0xda, 0xc7, 0x34,
0xd4, 0x90, 0x11, 0x64, 0x0e, 0x26, 0x18, 0x8f, 0x9f, 0xb4, 0xed, 0xa2, 0x1e, 0x6a, 0x3a, 0x6a,
0x0f, 0xb1, 0xa4, 0x84, 0x8e, 0x9b, 0x3d, 0x94, 0x74, 0x49, 0x05, 0xe6, 0xea, 0x3e, 0xbb, 0x83,
0xbc, 0x15, 0xf8, 0x58, 0xf5, 0xfd, 0xb8, 0x19, 0x69, 0xcd, 0x89, 0x1e, 0xd6, 0xd7, 0x47, 0x5c,
0x20, 0xba, 0x46, 0x6f, 0x4a, 0xc9, 0xae, 0x51, 0x11, 0xf8, 0xd5, 0xa6, 0x6c, 0xd8, 0xb3, 0x5a,
0xd8, 0x3e, 0x1e, 0x72, 0x15, 0xec, 0xa6, 0xc0, 0xea, 0x27, 0x4d, 0x8e, 0xf7, 0x62, 0xfe, 0x30,
0x8c, 0x69, 0x6d, 0xa3, 0x86, 0x91, 0x0c, 0x64, 0xdb, 0x9e, 0xd3, 0x5f, 0x0d, 0xf4, 0x3b, 0x47,
0xe1, 0x88, 0x2a, 0xef, 0xce, 0xfe, 0x73, 0x7e, 0xb6, 0xe0, 0xb8, 0x32, 0xac, 0x71, 0xa4, 0x12,
0x3d, 0x7c, 0xd4, 0x44, 0x21, 0xc9, 0x87, 0xb9, 0x8a, 0x9f, 0xae, 0xdc, 0x7c, 0xb9, 0xa3, 0xc8,
0x4b, 0x77, 0x74, 0xb2, 0x77, 0x4e, 0x40, 0xa1, 0xc9, 0x04, 0x72, 0x99, 0xec, 0xd0, 0xa4, 0xa7,
0xea, 0xca, 0xe7, 0x58, 0x13, 0xb7, 0xa2, 0xb0, 0xad, 0x37, 0xce, 0xa4, 0x97, 0x19, 0x9c, 0x47,
0x06, 0xf4, 0x2e, 0xab, 0x1d, 0x14, 0x68, 0xe5, 0xfb, 0x13, 0x26, 0xa7, 0x31, 0x26, 0x0b, 0x47,
0xbe, 0xb4, 0xe0, 0xd0, 0x66, 0x20, 0x24, 0x99, 0xcf, 0x1f, 0x56, 0xe9, 0xd1, 0x54, 0xda, 0xdc,
0x2b, 0x0a, 0x95, 0xc4, 0x39, 0xfd, 0xe9, 0x9f, 0xff, 0x7c, 0x3d, 0x76, 0x82, 0xcc, 0xe9, 0x2b,
0xb9, 0xb5, 0x9a, 0xdd, 0x7f, 0x01, 0x8a, 0xcf, 0xc7, 0x2c, 0xf2, 0x85, 0x05, 0xe3, 0x37, 0x70,
0x20, 0xcd, 0x9e, 0x69, 0xe2, 0x9c, 0xd1, 0x24, 0xa7, 0xc8, 0xc9, 0x7e, 0x24, 0xe5, 0xa7, 0xaa,
0xf7, 0x8c, 0x7c, 0x6b, 0xc1, 0xe4, 0x0d, 0x94, 0xf7, 0x78, 0x20, 0x71, 0xff, 0x91, 0xce, 0x6b,
0xa4, 0x33, 0xe4, 0x95, 0x0e, 0xd2, 0x63, 0x95, 0xf7, 0x62, 0x3f, 0xb0, 0x6f, 0x2c, 0x28, 0x2a,
0x41, 0xbd, 0x9c, 0xef, 0x60, 0x56, 0x70, 0x61, 0xd8, 0x0a, 0x92, 0x1f, 0x2d, 0x98, 0x57, 0xc3,
0xb4, 0x62, 0x07, 0x0f, 0xe7, 0x68, 0xb8, 0x05, 0x52, 0x1a, 0xac, 0x20, 0xf9, 0x08, 0x26, 0x8d,
0x72, 0x5b, 0x03, 0xa1, 0x8a, 0xdd, 0xe6, 0x2d, 0xe1, 0x2c, 0xeb, 0xc0, 0x0e, 0x59, 0x1a, 0x52,
0x2d, 0x65, 0xae, 0x42, 0x6e, 0x9b, 0xf0, 0xea, 0x59, 0x40, 0xfe, 0xdf, 0x1b, 0x3e, 0x7d, 0xd5,
0x95, 0x16, 0xfa, 0xb9, 0xd2, 0x73, 0x6c, 0x57, 0xe9, 0xa8, 0x4a, 0xf1, 0x95, 0x05, 0x33, 0x37,
0x50, 0x66, 0xef, 0x2f, 0x72, 0xba, 0x4f, 0xe4, 0xfc, 0xdb, 0xac, 0xe4, 0x0c, 0x1e, 0x90, 0x02,
0xbc, 0xa9, 0x01, 0x5e, 0x77, 0x2e, 0xf5, 0x07, 0x30, 0xaf, 0x24, 0x1d, 0xe7, 0xae, 0xb7, 0xa9,
0x51, 0x6a, 0x26, 0xc2, 0x55, 0x6b, 0x85, 0xb4, 0x34, 0xd2, 0x4d, 0x0c, 0xb7, 0xd7, 0x1a, 0x94,
0xcb, 0x81, 0x32, 0x2f, 0xe6, 0xcd, 0xd9, 0xf0, 0x14, 0xc2, 0xd5, 0x10, 0xcb, 0xe4, 0xdc, 0x30,
0x15, 0x1a, 0x18, 0x6e, 0xfb, 0x26, 0xcd, 0x77, 0x16, 0x14, 0xcc, 0xc9, 0x4f, 0x4e, 0xf5, 0x66,
0xec, 0xba, 0x11, 0xf6, 0x70, 0xcf, 0xbe, 0x6a, 0x2a, 0xce, 0xe9, 0xbb, 0x1d, 0xae, 0xea, 0x83,
0x57, 0x1d, 0x6b, 0x3f, 0x58, 0x50, 0xec, 0x20, 0x74, 0xbe, 0x3d, 0x38, 0x48, 0x67, 0x34, 0x24,
0xf9, 0xc5, 0x82, 0x79, 0x93, 0xbf, 0x7b, 0xef, 0x1e, 0x20, 0x66, 0x52, 0xf5, 0xce, 0x90, 0xdd,
0x9b, 0xc0, 0xfe, 0x64, 0x41, 0xc1, 0x5c, 0x9d, 0x3b, 0xe9, 0xba, 0xae, 0xd4, 0x3d, 0xa4, 0x5b,
0x35, 0xd5, 0x58, 0x1a, 0xb2, 0x27, 0x35, 0xca, 0xb3, 0x6c, 0xd5, 0x7f, 0xb5, 0xa0, 0xd8, 0xc1,
0x19, 0x2c, 0xe7, 0x7e, 0x01, 0xbb, 0x2f, 0x06, 0x4c, 0x7e, 0xb3, 0x60, 0xde, 0xb0, 0x8c, 0xac,
0x80, 0xfd, 0x42, 0x7e, 0x4d, 0x23, 0xbb, 0xa5, 0x73, 0xa3, 0x6e, 0xc0, 0x2e, 0x70, 0x0a, 0x85,
0x75, 0x0c, 0x71, 0xf0, 0x15, 0x6d, 0xf7, 0x9a, 0xd3, 0x23, 0xe6, 0x9c, 0x79, 0x05, 0xac, 0x0c,
0x7b, 0x05, 0xa8, 0x95, 0x6c, 0x40, 0xd1, 0xa4, 0xc8, 0xa9, 0xf2, 0xc2, 0xc9, 0xce, 0xec, 0x22,
0x19, 0x11, 0x30, 0x6f, 0x32, 0xf5, 0x2e, 0xc2, 0x0b, 0xa7, 0x4b, 0x9e, 0x13, 0x2b, 0xbb, 0x78,
0x4e, 0x3c, 0x85, 0xa3, 0xef, 0xd3, 0x30, 0x50, 0x8b, 0x6a, 0x7e, 0x2a, 0x92, 0x93, 0x3b, 0x2e,
0x89, 0xec, 0x27, 0xe4, 0x90, 0x9c, 0x15, 0x9d, 0xf3, 0x82, 0x73, 0x76, 0xd8, 0x91, 0xdd, 0x4a,
0x52, 0x25, 0xcb, 0xf7, 0x99, 0x05, 0xb3, 0x9d, 0xec, 0x7a, 0xd2, 0x2f, 0x87, 0x70, 0x45, 0x23,
0x54, 0x9c, 0x95, 0x91, 0xd3, 0xee, 0x01, 0xb9, 0x76, 0xfd, 0xf7, 0xe7, 0x8b, 0xd6, 0x1f, 0xcf,
0x17, 0xad, 0xbf, 0x9f, 0x2f, 0x5a, 0x1f, 0xbc, 0xb1, 0xbb, 0x7f, 0x87, 0x7c, 0xfd, 0xa3, 0x33,
0xf7, 0x3f, 0xce, 0xfd, 0x82, 0xfe, 0x23, 0xe7, 0xf2, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1e,
0x28, 0xd2, 0x55, 0xad, 0x12, 0x00, 0x00,
// 1349 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x98, 0xcf, 0x6f, 0x1c, 0x35,
0x14, 0xc7, 0x35, 0x49, 0xb3, 0x4d, 0x5e, 0x9a, 0x76, 0xeb, 0x24, 0x65, 0xd8, 0xa6, 0x69, 0x98,
0x96, 0x2a, 0x8d, 0xda, 0xd9, 0x66, 0x0b, 0xa2, 0x2a, 0x02, 0x69, 0x9b, 0x54, 0x6d, 0x44, 0x44,
0xcb, 0x94, 0x52, 0x09, 0x81, 0x90, 0x33, 0xfb, 0xb2, 0x3b, 0xcd, 0x64, 0xc6, 0xb5, 0xbd, 0xdb,
0x2e, 0x55, 0x2f, 0x1c, 0x10, 0x12, 0x5c, 0x10, 0x02, 0x71, 0x02, 0x0e, 0x48, 0x48, 0x70, 0xe7,
0x6f, 0xe0, 0x88, 0xc4, 0x3f, 0x80, 0x2a, 0xfe, 0x08, 0x6e, 0x20, 0xdb, 0xb3, 0x33, 0xb3, 0xc9,
0xfe, 0x48, 0xd5, 0x34, 0x37, 0xfb, 0xd9, 0xf3, 0xde, 0xc7, 0x5f, 0xbf, 0x67, 0x7b, 0x17, 0x1c,
0x81, 0xbc, 0x85, 0xbc, 0xcc, 0x91, 0xc5, 0x22, 0x90, 0x31, 0x6f, 0xe7, 0x9a, 0x2e, 0xe3, 0xb1,
0x8c, 0x09, 0x64, 0x96, 0xd2, 0x5c, 0x3d, 0x8e, 0xeb, 0x21, 0x96, 0x29, 0x0b, 0xca, 0x34, 0x8a,
0x62, 0x49, 0x65, 0x10, 0x47, 0xc2, 0xcc, 0x2c, 0xad, 0xd7, 0x03, 0xd9, 0x68, 0x6e, 0xb8, 0x7e,
0xbc, 0x5d, 0xa6, 0xbc, 0x1e, 0x33, 0x1e, 0xdf, 0xd7, 0x8d, 0x8b, 0x7e, 0xad, 0xdc, 0xba, 0x5c,
0x66, 0x5b, 0x75, 0xf5, 0xa5, 0x28, 0x53, 0xc6, 0xc2, 0xc0, 0xd7, 0xdf, 0x96, 0x5b, 0xcb, 0x34,
0x64, 0x0d, 0xba, 0x5c, 0xae, 0x63, 0x84, 0x9c, 0x4a, 0xac, 0x25, 0xde, 0xae, 0x0f, 0xf1, 0xa6,
0xb1, 0x86, 0xe2, 0x3b, 0x6d, 0x98, 0xf2, 0x90, 0xc5, 0x55, 0xc6, 0xc4, 0x7b, 0x4d, 0xe4, 0x6d,
0x42, 0xe0, 0x90, 0x9a, 0x64, 0x5b, 0x0b, 0xd6, 0xe2, 0x84, 0xa7, 0xdb, 0xa4, 0x04, 0xe3, 0x1c,
0x5b, 0x81, 0x08, 0xe2, 0xc8, 0x1e, 0xd1, 0xf6, 0xb4, 0x4f, 0x6c, 0x38, 0x4c, 0x19, 0x7b, 0x97,
0x6e, 0xa3, 0x3d, 0xaa, 0x87, 0x3a, 0x5d, 0x32, 0x0f, 0x40, 0x19, 0xbb, 0xcd, 0xe3, 0xfb, 0xe8,
0x4b, 0xfb, 0x90, 0x1e, 0xcc, 0x59, 0x9c, 0x65, 0x38, 0x5c, 0x65, 0x6c, 0x2d, 0xda, 0x8c, 0x55,
0x50, 0xd9, 0x66, 0xd8, 0x09, 0xaa, 0xda, 0xca, 0xc6, 0xa8, 0x6c, 0x24, 0x01, 0x75, 0xdb, 0xf9,
0xd7, 0x82, 0xe9, 0x04, 0x77, 0x15, 0x25, 0x0d, 0xc2, 0x04, 0xba, 0x0e, 0x05, 0x11, 0x37, 0xb9,
0x6f, 0x3c, 0x4c, 0x56, 0x6e, 0xb9, 0x99, 0x3a, 0x6e, 0x47, 0x1d, 0xdd, 0xf8, 0xc4, 0xaf, 0xb9,
0xad, 0xcb, 0x2e, 0xdb, 0xaa, 0xbb, 0x4a, 0x6b, 0x37, 0xa7, 0xb5, 0xdb, 0xd1, 0xda, 0xad, 0x66,
0xc6, 0x3b, 0xda, 0xad, 0x97, 0xb8, 0xcf, 0xaf, 0x76, 0x64, 0xd0, 0x6a, 0x47, 0x77, 0xae, 0x96,
0x2c, 0xc0, 0xa4, 0xf1, 0xb1, 0x16, 0xd5, 0xf0, 0x91, 0x96, 0x63, 0xcc, 0xcb, 0x9b, 0xc8, 0x1c,
0x4c, 0xb4, 0x90, 0x2b, 0x51, 0xd7, 0x6a, 0xf6, 0x98, 0x1e, 0xcf, 0x0c, 0xce, 0x5b, 0x50, 0xec,
0x6c, 0x94, 0x87, 0x82, 0xc5, 0x91, 0x40, 0x72, 0x1e, 0xc6, 0x02, 0x89, 0xdb, 0xc2, 0xb6, 0x16,
0x46, 0x17, 0x27, 0x2b, 0xd3, 0x6e, 0x6e, 0x7b, 0x13, 0x69, 0x3d, 0x33, 0xc3, 0xf1, 0x61, 0x42,
0x7d, 0xde, 0x7f, 0x8f, 0x1d, 0x38, 0xb2, 0x19, 0xab, 0xa5, 0xe2, 0x26, 0x47, 0x61, 0x64, 0x1f,
0xf7, 0xba, 0x6c, 0xc3, 0xd6, 0xe8, 0xfc, 0x37, 0x06, 0xc7, 0x34, 0xa4, 0xef, 0xa3, 0x18, 0x9c,
0x4f, 0x4d, 0x81, 0x3c, 0xca, 0x64, 0x4c, 0xfb, 0x6a, 0x8c, 0x51, 0x21, 0x1e, 0xc6, 0xbc, 0x96,
0x44, 0x48, 0xfb, 0xe4, 0x2c, 0x4c, 0x09, 0xd1, 0xb8, 0xcd, 0x83, 0x16, 0x95, 0xf8, 0x0e, 0xb6,
0x93, 0xa4, 0xea, 0x36, 0x2a, 0x0f, 0x41, 0x24, 0xd0, 0x6f, 0x72, 0xd4, 0x32, 0x8e, 0x7b, 0x69,
0x9f, 0x5c, 0x80, 0xe3, 0x32, 0x14, 0x2b, 0x61, 0x80, 0x91, 0x5c, 0x41, 0x2e, 0x57, 0xa9, 0xa4,
0x76, 0x41, 0x7b, 0xd9, 0x3d, 0x40, 0x96, 0xa0, 0xd8, 0x65, 0x54, 0x21, 0x0f, 0xeb, 0xc9, 0xbb,
0xec, 0x69, 0x0a, 0x4f, 0x74, 0xa7, 0xb0, 0x5e, 0x23, 0x18, 0x9b, 0x5e, 0xdf, 0x1c, 0x4c, 0x60,
0x44, 0x37, 0x42, 0xbc, 0xe5, 0x07, 0xf6, 0xa4, 0xc6, 0xcb, 0x0c, 0xe4, 0x12, 0x4c, 0x9b, 0xcc,
0xad, 0x2a, 0x55, 0xd3, 0x75, 0x1e, 0xd1, 0x0e, 0x7a, 0x0d, 0xa9, 0xbc, 0x4a, 0xcd, 0x6b, 0xab,
0xf6, 0xd4, 0x82, 0xb5, 0x38, 0xea, 0xe5, 0x4d, 0xe4, 0x0a, 0xbc, 0x94, 0x75, 0x23, 0x21, 0x69,
0x18, 0xea, 0xd4, 0x5e, 0x5b, 0xb5, 0x8f, 0xea, 0xd9, 0xfd, 0x86, 0xc9, 0xdb, 0x50, 0x4a, 0x87,
0xae, 0x47, 0x12, 0x39, 0xe3, 0x81, 0xc0, 0x6b, 0x54, 0xe0, 0x5d, 0x1e, 0xda, 0xc7, 0x34, 0xd4,
0x80, 0x19, 0x64, 0x06, 0xc6, 0x18, 0x8f, 0x1f, 0xb5, 0xed, 0xa2, 0x9e, 0x6a, 0x3a, 0xaa, 0x86,
0x58, 0x92, 0x42, 0xc7, 0x4d, 0x0d, 0x25, 0x5d, 0x52, 0x81, 0x99, 0xba, 0xcf, 0xee, 0x20, 0x6f,
0x05, 0x3e, 0x56, 0x7d, 0x3f, 0x6e, 0x46, 0x5a, 0x73, 0xa2, 0xa7, 0xf5, 0x1c, 0x23, 0x2e, 0x10,
0x9d, 0xa3, 0x37, 0xa5, 0x64, 0xd7, 0xa8, 0x08, 0xfc, 0x6a, 0x53, 0x36, 0xec, 0x69, 0x2d, 0x6c,
0x8f, 0x11, 0x72, 0x15, 0xec, 0xa6, 0xc0, 0xea, 0xa7, 0x4d, 0x8e, 0xf7, 0x62, 0xbe, 0x15, 0xc6,
0xb4, 0xb6, 0x56, 0xc3, 0x48, 0x06, 0xb2, 0x6d, 0xcf, 0xe8, 0xaf, 0xfa, 0x8e, 0x2b, 0xad, 0x37,
0x90, 0x72, 0xe4, 0xef, 0xc7, 0x5b, 0x18, 0xd9, 0xb3, 0x1a, 0x2b, 0x6f, 0x72, 0x8e, 0xc2, 0x11,
0x55, 0x00, 0x9d, 0x0a, 0x75, 0x7e, 0xb1, 0xe0, 0xb8, 0x32, 0xac, 0x70, 0xa4, 0x12, 0x3d, 0x7c,
0xd0, 0x44, 0x21, 0xc9, 0x47, 0xb9, 0x9a, 0x98, 0xac, 0xdc, 0x7c, 0xbe, 0xc3, 0xca, 0x4b, 0x6b,
0x3e, 0xa9, 0xae, 0x13, 0x50, 0x68, 0x32, 0x81, 0x5c, 0x26, 0x35, 0x9c, 0xf4, 0x54, 0xe6, 0xf9,
0x1c, 0x6b, 0xe2, 0x56, 0x14, 0xb6, 0x75, 0x69, 0x8d, 0x7b, 0x99, 0xc1, 0x79, 0x60, 0x40, 0xef,
0xb2, 0xda, 0x41, 0x81, 0x56, 0x7e, 0x38, 0x61, 0x62, 0x1a, 0x63, 0xb2, 0xb5, 0xe4, 0x2b, 0x0b,
0x0e, 0xad, 0x07, 0x42, 0x92, 0xd9, 0xfc, 0x71, 0x96, 0x1e, 0x5e, 0xa5, 0xf5, 0xfd, 0xa2, 0x50,
0x41, 0x9c, 0xd3, 0x9f, 0xfd, 0xf5, 0xcf, 0x37, 0x23, 0x27, 0xc8, 0x8c, 0xbe, 0xb4, 0x5b, 0xcb,
0xd9, 0x0d, 0x19, 0xa0, 0xf8, 0x62, 0xc4, 0x22, 0x5f, 0x5a, 0x30, 0x7a, 0x03, 0xfb, 0xd2, 0xec,
0x9b, 0x26, 0xce, 0x19, 0x4d, 0x72, 0x8a, 0x9c, 0xec, 0x45, 0x52, 0x7e, 0xac, 0x7a, 0x4f, 0xc8,
0x77, 0x16, 0x8c, 0xdf, 0x40, 0x79, 0x8f, 0x07, 0x12, 0x5f, 0x3c, 0xd2, 0x79, 0x8d, 0x74, 0x86,
0xbc, 0xd2, 0x41, 0x7a, 0xa8, 0xe2, 0x5e, 0xec, 0x05, 0xf6, 0xad, 0x05, 0x45, 0x25, 0xa8, 0x97,
0x1b, 0x3b, 0x98, 0x1d, 0x9c, 0x1b, 0xb4, 0x83, 0xe4, 0x27, 0x0b, 0x66, 0xd5, 0x34, 0xad, 0xd8,
0xc1, 0xc3, 0x39, 0x1a, 0x6e, 0x8e, 0x94, 0xfa, 0x2b, 0x48, 0x3e, 0x86, 0x71, 0xa3, 0xdc, 0x66,
0x5f, 0xa8, 0x62, 0xb7, 0x79, 0x53, 0x38, 0x8b, 0xda, 0xb1, 0x43, 0x16, 0x06, 0x64, 0x4b, 0x99,
0x2b, 0x97, 0xdb, 0xc6, 0xbd, 0x7a, 0x38, 0x90, 0x97, 0x77, 0xba, 0x4f, 0xdf, 0x7d, 0xa5, 0xb9,
0x5e, 0x43, 0xe9, 0x39, 0xb6, 0xa7, 0x70, 0x54, 0x85, 0xf8, 0xda, 0x82, 0xa9, 0x1b, 0x28, 0xb3,
0x17, 0x1a, 0x39, 0xdd, 0xc3, 0x73, 0xfe, 0xf5, 0x56, 0x72, 0xfa, 0x4f, 0x48, 0x01, 0xde, 0xd4,
0x00, 0xaf, 0x3b, 0x97, 0x7a, 0x03, 0x98, 0x77, 0x94, 0xf6, 0x73, 0xd7, 0x5b, 0xd7, 0x28, 0x35,
0xe3, 0xe1, 0xaa, 0xb5, 0x44, 0x5a, 0x1a, 0xe9, 0x26, 0x86, 0xdb, 0x2b, 0x0d, 0xca, 0x65, 0x5f,
0x99, 0xe7, 0xf3, 0xe6, 0x6c, 0x7a, 0x0a, 0xe1, 0x6a, 0x88, 0x45, 0x72, 0x6e, 0x90, 0x0a, 0x0d,
0x0c, 0xb7, 0x7d, 0x13, 0xe6, 0x7b, 0x0b, 0x0a, 0xe6, 0xe4, 0x27, 0xa7, 0x76, 0x46, 0xec, 0xba,
0x11, 0xf6, 0xb1, 0x66, 0x5f, 0x35, 0x19, 0xe7, 0xf4, 0x2c, 0x87, 0xab, 0xfa, 0xe0, 0x55, 0xc7,
0xda, 0x8f, 0x16, 0x14, 0x3b, 0x08, 0x9d, 0x6f, 0x0f, 0x0e, 0xd2, 0x19, 0x0e, 0x49, 0x7e, 0xb5,
0x60, 0xd6, 0xc4, 0xef, 0xae, 0xdd, 0x03, 0xc4, 0x4c, 0xb2, 0xde, 0x19, 0x50, 0xbd, 0x09, 0xec,
0xcf, 0x16, 0x14, 0xcc, 0xd5, 0xb9, 0x9b, 0xae, 0xeb, 0x4a, 0xdd, 0x47, 0xba, 0x65, 0x93, 0x8d,
0xa5, 0x01, 0x35, 0xa9, 0x51, 0x9e, 0x64, 0xbb, 0xfe, 0x9b, 0x05, 0xc5, 0x0e, 0x4e, 0x7f, 0x39,
0x5f, 0x14, 0xb0, 0xfb, 0x6c, 0xc0, 0xe4, 0x77, 0x0b, 0x66, 0x0d, 0xcb, 0xd0, 0x0c, 0x78, 0x51,
0xc8, 0xaf, 0x69, 0x64, 0xb7, 0x74, 0x6e, 0xd8, 0x0d, 0xd8, 0x05, 0x4e, 0xa1, 0xb0, 0x8a, 0x21,
0xf6, 0xbf, 0xa2, 0xed, 0x9d, 0xe6, 0xf4, 0x88, 0x39, 0x67, 0x5e, 0x01, 0x4b, 0x83, 0x5e, 0x01,
0x6a, 0x27, 0x1b, 0x50, 0x34, 0x21, 0x72, 0xaa, 0x3c, 0x73, 0xb0, 0x33, 0x7b, 0x08, 0x46, 0x04,
0xcc, 0x9a, 0x48, 0x3b, 0x37, 0xe1, 0x99, 0xc3, 0x25, 0xcf, 0x89, 0xa5, 0x3d, 0x3c, 0x27, 0x1e,
0xc3, 0xd1, 0x0f, 0x68, 0x18, 0xa8, 0x4d, 0x35, 0x3f, 0x26, 0xc9, 0xc9, 0x5d, 0x97, 0x44, 0xf6,
0x23, 0x73, 0x40, 0xcc, 0x8a, 0x8e, 0x79, 0xc1, 0x39, 0x3b, 0xe8, 0xc8, 0x6e, 0x25, 0xa1, 0x92,
0xed, 0xfb, 0xdc, 0x82, 0xe9, 0x4e, 0x74, 0xbd, 0xe8, 0xe7, 0x43, 0xb8, 0xa2, 0x11, 0x2a, 0xce,
0xd2, 0xd0, 0x65, 0xef, 0x00, 0xb9, 0x76, 0xfd, 0x8f, 0xa7, 0xf3, 0xd6, 0x9f, 0x4f, 0xe7, 0xad,
0xbf, 0x9f, 0xce, 0x5b, 0x1f, 0xbe, 0xb1, 0xb7, 0xff, 0x8f, 0x7c, 0xfd, 0xb3, 0x34, 0xf7, 0x4f,
0xcf, 0x46, 0x41, 0xff, 0xd5, 0x73, 0xf9, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb8, 0x97, 0xe0,
0xc3, 0xcf, 0x12, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@@ -1907,6 +1917,15 @@ func (m *RepoAccessQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.BearerToken) > 0 {
i -= len(m.BearerToken)
copy(dAtA[i:], m.BearerToken)
i = encodeVarintRepository(dAtA, i, uint64(len(m.BearerToken)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xaa
}
if m.UseAzureWorkloadIdentity {
i--
if m.UseAzureWorkloadIdentity {
@@ -2392,6 +2411,10 @@ func (m *RepoAccessQuery) Size() (n int) {
if m.UseAzureWorkloadIdentity {
n += 3
}
l = len(m.BearerToken)
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -3720,6 +3743,38 @@ func (m *RepoAccessQuery) Unmarshal(dAtA []byte) error {
}
}
m.UseAzureWorkloadIdentity = bool(v != 0)
case 21:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field BearerToken", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.BearerToken = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])

File diff suppressed because it is too large Load Diff

View File

@@ -1712,6 +1712,9 @@ message RepoCreds {
// UseAzureWorkloadIdentity specifies whether to use Azure Workload Identity for authentication
optional bool useAzureWorkloadIdentity = 24;
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
optional string bearerToken = 25;
}
// RepositoryList is a collection of Repositories.
@@ -1795,6 +1798,9 @@ message Repository {
// UseAzureWorkloadIdentity specifies whether to use Azure Workload Identity for authentication
optional bool useAzureWorkloadIdentity = 24;
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
optional string bearerToken = 25;
}
// A RepositoryCertificate is either SSH known hosts entry or TLS certificate

View File

@@ -51,6 +51,8 @@ type RepoCreds struct {
NoProxy string `json:"noProxy,omitempty" protobuf:"bytes,23,opt,name=noProxy"`
// UseAzureWorkloadIdentity specifies whether to use Azure Workload Identity for authentication
UseAzureWorkloadIdentity bool `json:"useAzureWorkloadIdentity,omitempty" protobuf:"bytes,24,opt,name=useAzureWorkloadIdentity"`
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,25,opt,name=bearerToken"`
}
// Repository is a repository holding application configurations
@@ -104,6 +106,8 @@ type Repository struct {
NoProxy string `json:"noProxy,omitempty" protobuf:"bytes,23,opt,name=noProxy"`
// UseAzureWorkloadIdentity specifies whether to use Azure Workload Identity for authentication
UseAzureWorkloadIdentity bool `json:"useAzureWorkloadIdentity,omitempty" protobuf:"bytes,24,opt,name=useAzureWorkloadIdentity"`
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,25,opt,name=bearerToken"`
}
// IsInsecure returns true if the repository has been configured to skip server verification
@@ -118,7 +122,7 @@ func (repo *Repository) IsLFSEnabled() bool {
// HasCredentials returns true when the repository has been configured with any credentials
func (repo *Repository) HasCredentials() bool {
return repo.Username != "" || repo.Password != "" || repo.SSHPrivateKey != "" || repo.TLSClientCertData != "" || repo.GithubAppPrivateKey != "" || repo.UseAzureWorkloadIdentity
return repo.Username != "" || repo.Password != "" || repo.BearerToken != "" || repo.SSHPrivateKey != "" || repo.TLSClientCertData != "" || repo.GithubAppPrivateKey != "" || repo.UseAzureWorkloadIdentity
}
// CopyCredentialsFromRepo copies all credential information from source repository to receiving repository
@@ -130,6 +134,9 @@ func (repo *Repository) CopyCredentialsFromRepo(source *Repository) {
if repo.Password == "" {
repo.Password = source.Password
}
if repo.BearerToken == "" {
repo.BearerToken = source.BearerToken
}
if repo.SSHPrivateKey == "" {
repo.SSHPrivateKey = source.SSHPrivateKey
}
@@ -168,6 +175,9 @@ func (repo *Repository) CopyCredentialsFrom(source *RepoCreds) {
if repo.Password == "" {
repo.Password = source.Password
}
if repo.BearerToken == "" {
repo.BearerToken = source.BearerToken
}
if repo.SSHPrivateKey == "" {
repo.SSHPrivateKey = source.SSHPrivateKey
}
@@ -208,8 +218,8 @@ func (repo *Repository) GetGitCreds(store git.CredsStore) git.Creds {
if repo == nil {
return git.NopCreds{}
}
if repo.Password != "" {
return git.NewHTTPSCreds(repo.Username, repo.Password, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy, repo.NoProxy, store, repo.ForceHttpBasicAuth)
if repo.Password != "" || repo.BearerToken != "" {
return git.NewHTTPSCreds(repo.Username, repo.Password, repo.BearerToken, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy, repo.NoProxy, store, repo.ForceHttpBasicAuth)
}
if repo.SSHPrivateKey != "" {
return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure(), store, repo.Proxy, repo.NoProxy)

View File

@@ -3,6 +3,7 @@ package v1alpha1
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v3/util/git"
@@ -35,3 +36,67 @@ func TestGetHelmCredsShouldReturnHelmCredsIfAzureWorkloadIdentityNotSpecified(t
_, ok := creds.(helm.HelmCreds)
require.Truef(t, ok, "expected HelmCreds but got %T", creds)
}
func TestGetGitCreds(t *testing.T) {
tests := []struct {
name string
repo *Repository
expected git.Creds
}{
{
name: "nil repository",
repo: nil,
expected: git.NopCreds{},
},
{
name: "HTTPS credentials",
repo: &Repository{
Username: "user",
Password: "pass",
},
expected: git.NewHTTPSCreds("user", "pass", "", "", "", false, "", "", nil, false),
},
{
name: "Bearer token credentials",
repo: &Repository{
BearerToken: "token",
},
expected: git.NewHTTPSCreds("", "", "token", "", "", false, "", "", nil, false),
},
{
name: "SSH credentials",
repo: &Repository{
SSHPrivateKey: "ssh-key",
},
expected: git.NewSSHCreds("ssh-key", "", false, nil, "", ""),
},
{
name: "GitHub App credentials",
repo: &Repository{
GithubAppPrivateKey: "github-key",
GithubAppId: 123,
GithubAppInstallationId: 456,
},
expected: git.NewGitHubAppCreds(123, 456, "github-key", "", "", "", "", false, "", "", nil),
},
{
name: "Google Cloud credentials",
repo: &Repository{
GCPServiceAccountKey: "gcp-key",
},
expected: git.NewGoogleCloudCreds("gcp-key", nil),
},
{
name: "No credentials",
repo: &Repository{},
expected: git.NopCreds{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
creds := tt.repo.GetGitCreds(nil)
assert.Equal(t, tt.expected, creds)
})
}
}

View File

@@ -1139,6 +1139,11 @@ func TestRepository_HasCredentials(t *testing.T) {
repo: Repository{Password: "foo"},
want: true,
},
{
name: "TestHasBearerToken",
repo: Repository{BearerToken: "foo"},
want: true,
},
{
name: "TestHasSSHPrivateKey",
repo: Repository{SSHPrivateKey: "foo"},
@@ -1249,6 +1254,7 @@ func TestRepository_CopyCredentialsFromRepo(t *testing.T) {
}{
{"Username", &Repository{Username: "foo"}, &Repository{}, Repository{Username: "foo"}},
{"Password", &Repository{Password: "foo"}, &Repository{}, Repository{Password: "foo"}},
{"BearerToken", &Repository{BearerToken: "foo"}, &Repository{}, Repository{BearerToken: "foo"}},
{"SSHPrivateKey", &Repository{SSHPrivateKey: "foo"}, &Repository{}, Repository{SSHPrivateKey: "foo"}},
{"InsecureHostKey", &Repository{InsecureIgnoreHostKey: true}, &Repository{}, Repository{InsecureIgnoreHostKey: true}},
{"Insecure", &Repository{Insecure: true}, &Repository{}, Repository{Insecure: true}},
@@ -1259,6 +1265,7 @@ func TestRepository_CopyCredentialsFromRepo(t *testing.T) {
{"SourceUsername", &Repository{}, &Repository{Username: "foo"}, Repository{Username: "foo"}},
{"SourcePassword", &Repository{}, &Repository{Password: "foo"}, Repository{Password: "foo"}},
{"SourcePassword", &Repository{}, &Repository{BearerToken: "foo"}, Repository{BearerToken: "foo"}},
{"SourceSSHPrivateKey", &Repository{}, &Repository{SSHPrivateKey: "foo"}, Repository{SSHPrivateKey: "foo"}},
{"SourceInsecureHostKey", &Repository{}, &Repository{InsecureIgnoreHostKey: true}, Repository{InsecureIgnoreHostKey: false}},
{"SourceInsecure", &Repository{}, &Repository{Insecure: true}, Repository{Insecure: false}},
@@ -1284,6 +1291,7 @@ func TestRepository_CopyCredentialsFrom(t *testing.T) {
}{
{"Username", &Repository{Username: "foo"}, &RepoCreds{}, Repository{Username: "foo"}},
{"Password", &Repository{Password: "foo"}, &RepoCreds{}, Repository{Password: "foo"}},
{"BearerToken", &Repository{BearerToken: "foo"}, &RepoCreds{}, Repository{BearerToken: "foo"}},
{"SSHPrivateKey", &Repository{SSHPrivateKey: "foo"}, &RepoCreds{}, Repository{SSHPrivateKey: "foo"}},
{"InsecureHostKey", &Repository{InsecureIgnoreHostKey: true}, &RepoCreds{}, Repository{InsecureIgnoreHostKey: true}},
{"Insecure", &Repository{Insecure: true}, &RepoCreds{}, Repository{Insecure: true}},
@@ -1294,6 +1302,7 @@ func TestRepository_CopyCredentialsFrom(t *testing.T) {
{"SourceUsername", &Repository{}, &RepoCreds{Username: "foo"}, Repository{Username: "foo"}},
{"SourcePassword", &Repository{}, &RepoCreds{Password: "foo"}, Repository{Password: "foo"}},
{"SourceBearerToken", &Repository{}, &RepoCreds{BearerToken: "foo"}, Repository{BearerToken: "foo"}},
{"SourceSSHPrivateKey", &Repository{}, &RepoCreds{SSHPrivateKey: "foo"}, Repository{SSHPrivateKey: "foo"}},
{"SourceTLSClientCertData", &Repository{}, &RepoCreds{TLSClientCertData: "foo"}, Repository{TLSClientCertData: "foo"}},
{"SourceTLSClientCertKey", &Repository{}, &RepoCreds{TLSClientCertKey: "foo"}, Repository{TLSClientCertKey: "foo"}},

View File

@@ -671,6 +671,7 @@ func (s *Server) ValidateAccess(ctx context.Context, q *repositorypkg.RepoAccess
Name: q.Name,
Username: q.Username,
Password: q.Password,
BearerToken: q.BearerToken,
SSHPrivateKey: q.SshPrivateKey,
Insecure: q.Insecure,
TLSClientCertData: q.TlsClientCertData,
@@ -719,6 +720,7 @@ func (s *Server) ValidateWriteAccess(ctx context.Context, q *repositorypkg.RepoA
Name: q.Name,
Username: q.Username,
Password: q.Password,
BearerToken: q.BearerToken,
SSHPrivateKey: q.SshPrivateKey,
Insecure: q.Insecure,
TLSClientCertData: q.TlsClientCertData,

View File

@@ -91,6 +91,8 @@ message RepoAccessQuery {
bool forceHttpBasicAuth = 19;
// Whether to use azure workload identity for authentication
bool useAzureWorkloadIdentity = 20;
// BearerToken contains the bearer token used for Git auth at the repo server
string bearerToken = 21;
}
message RepoResponse {}

View File

@@ -122,6 +122,7 @@ const (
RepoURLTypeHelmOCI = "helm-oci"
GitUsername = "admin"
GitPassword = "password"
GitBearerToken = "test"
GithubAppID = "2978632978"
GithubAppInstallationID = "7893789433789"
GpgGoodKeyID = "D56C4FCA57A46444"

View File

@@ -175,3 +175,32 @@ func TestAddHelmRepoInsecureSkipVerify(t *testing.T) {
assert.True(t, exists)
})
}
func TestFailOnPrivateRepoCreationWithPasswordAndBearerToken(t *testing.T) {
app.Given(t).And(func() {
repoURL := fixture.RepoURL(fixture.RepoURLTypeFile)
_, err := fixture.RunCli("repo", "add", repoURL, "--password", fixture.GitPassword, "--bearer-token", fixture.GitBearerToken)
require.ErrorContains(t, err, "only --bearer-token or --password is allowed, not both")
})
}
func TestFailOnCreatePrivateNonHTTPSRepoWithBearerToken(t *testing.T) {
app.Given(t).And(func() {
repoURL := fixture.RepoURL(fixture.RepoURLTypeFile)
_, err := fixture.RunCli("repo", "add", repoURL, "--bearer-token", fixture.GitBearerToken)
require.ErrorContains(t, err, "--bearer-token is only supported for HTTPS repositories")
})
}
func TestFailOnCreatePrivateNonGitRepoWithBearerToken(t *testing.T) {
app.Given(t).And(func() {
repoURL := fixture.RepoURL(fixture.RepoURLTypeHelm)
_, err := fixture.RunCli("repo", "add", repoURL, "--bearer-token", fixture.GitBearerToken,
"--insecure-skip-server-verification",
"--tls-client-cert-path", repos.CertPath,
"--tls-client-cert-key-path", repos.CertKeyPath,
"--name", "testrepo",
"--type", "helm")
require.ErrorContains(t, err, "--bearer-token is only supported for Git repositories")
})
}

View File

@@ -37,6 +37,14 @@ export const RepoDetails = (props: {repo: models.Repository; save?: (params: New
}
];
if (repository.type === 'git') {
items.push({
title: 'Bearer token (optional, for BitBucket Data Center only)',
view: repository.bearerToken ? '******' : '',
edit: (formApi: FormApi) => <FormField formApi={formApi} field='bearerToken' component={Text} componentProps={{type: 'password'}} />
});
}
if (useAuthSettingsCtx?.hydratorEnabled) {
// Insert this item at index 1.
const item = {
@@ -77,6 +85,7 @@ export const RepoDetails = (props: {repo: models.Repository; save?: (params: New
url: repo.repo,
username: repo.username || '',
password: repo.password || '',
bearerToken: repo.bearerToken || '',
tlsClientCertData: repo.tlsClientCertData || '',
tlsClientCertKey: repo.tlsClientCertKey || '',
insecure: repo.insecure || false,
@@ -94,13 +103,15 @@ export const RepoDetails = (props: {repo: models.Repository; save?: (params: New
values={repo}
validate={input => ({
username: !input.username && input.password && 'Username is required if password is given.',
password: !input.password && input.username && 'Password is required if username is given.'
password: !input.password && input.username && 'Password is required if username is given.',
bearerToken: input.password && input.bearerToken && 'Either the password or the bearer token must be set, but not both.'
})}
save={async input => {
const params: NewHTTPSRepoParams = {...newRepo, write};
params.name = input.name || '';
params.username = input.username || '';
params.password = input.password || '';
params.bearerToken = input.bearerToken || '';
save(params);
}}
title='CONNECTED REPOSITORY'

View File

@@ -33,6 +33,7 @@ export interface NewHTTPSRepoParams {
url: string;
username: string;
password: string;
bearerToken: string;
tlsClientCertData: string;
tlsClientCertKey: string;
insecure: boolean;
@@ -89,6 +90,7 @@ interface NewHTTPSRepoCredsParams {
url: string;
username: string;
password: string;
bearerToken: string;
tlsClientCertData: string;
tlsClientCertKey: string;
proxy: string;
@@ -214,6 +216,9 @@ export class ReposList extends React.Component<
name: httpsValues.type === 'helm' && !httpsValues.name && 'Name is required',
username: !httpsValues.username && httpsValues.password && 'Username is required if password is given.',
password: !httpsValues.password && httpsValues.username && 'Password is required if username is given.',
bearerToken:
(httpsValues.password && httpsValues.bearerToken && 'Either the password or the bearer token must be set, but not both.') ||
(httpsValues.type != 'git' && 'Bearer token is only supported for Git BitBucket Data Center repositories'),
tlsClientCertKey: !httpsValues.tlsClientCertKey && httpsValues.tlsClientCertData && 'TLS client cert key is required if TLS client cert is given.'
};
case ConnectionMethod.GITHUBAPP:
@@ -681,6 +686,17 @@ export class ReposList extends React.Component<
componentProps={{type: 'password'}}
/>
</div>
{formApi.getFormState().values.type === 'git' && (
<div className='argo-form-row'>
<FormField
formApi={formApi}
label='Bearer token (optional, for BitBucket Data Center only)'
field='bearerToken'
component={Text}
componentProps={{type: 'password'}}
/>
</div>
)}
<div className='argo-form-row'>
<FormField formApi={formApi} label='TLS client certificate (optional)' field='tlsClientCertData' component={TextArea} />
</div>
@@ -920,6 +936,7 @@ export class ReposList extends React.Component<
url: params.url,
username: params.username,
password: params.password,
bearerToken: params.bearerToken,
tlsClientCertData: params.tlsClientCertData,
tlsClientCertKey: params.tlsClientCertKey,
proxy: params.proxy,

View File

@@ -598,6 +598,7 @@ export interface Repository {
project?: string;
username?: string;
password?: string;
bearerToken?: string;
tlsClientCertData?: string;
tlsClientCertKey?: string;
proxy?: string;
@@ -615,6 +616,7 @@ export interface RepositoryList extends ItemsList<Repository> {}
export interface RepoCreds {
url: string;
username?: string;
bearerToken?: string;
}
export interface RepoCredsList extends ItemsList<RepoCreds> {}

View File

@@ -7,6 +7,7 @@ export interface HTTPSQuery {
url: string;
username: string;
password: string;
bearerToken: string;
tlsClientCertData: string;
tlsClientCertKey: string;
insecure: boolean;
@@ -96,6 +97,7 @@ export class RepositoriesService {
repo: q.url,
username: q.username,
password: q.password,
bearerToken: q.bearerToken,
tlsClientCertData: q.tlsClientCertData,
tlsClientCertKey: q.tlsClientCertKey,
insecure: q.insecure,
@@ -119,6 +121,7 @@ export class RepositoriesService {
repo: q.url,
username: q.username,
password: q.password,
bearerToken: q.bearerToken,
tlsClientCertData: q.tlsClientCertData,
tlsClientCertKey: q.tlsClientCertKey,
insecure: q.insecure,
@@ -142,6 +145,7 @@ export class RepositoriesService {
repo: q.url,
username: q.username,
password: q.password,
bearerToken: q.bearerToken,
tlsClientCertData: q.tlsClientCertData,
tlsClientCertKey: q.tlsClientCertKey,
insecure: q.insecure,
@@ -165,6 +169,7 @@ export class RepositoriesService {
repo: q.url,
username: q.username,
password: q.password,
bearerToken: q.bearerToken,
tlsClientCertData: q.tlsClientCertData,
tlsClientCertKey: q.tlsClientCertKey,
insecure: q.insecure,

View File

@@ -5,6 +5,7 @@ export interface HTTPSCreds {
url: string;
username: string;
password: string;
bearerToken: string;
tlsClientCertData: string;
tlsClientCertKey: string;
proxy: string;

View File

@@ -306,6 +306,7 @@ func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) {
Repo: string(secret.Data["url"]),
Username: string(secret.Data["username"]),
Password: string(secret.Data["password"]),
BearerToken: string(secret.Data["bearerToken"]),
SSHPrivateKey: string(secret.Data["sshPrivateKey"]),
TLSClientCertData: string(secret.Data["tlsClientCertData"]),
TLSClientCertKey: string(secret.Data["tlsClientCertKey"]),
@@ -379,6 +380,7 @@ func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Reposit
updateSecretString(secret, "url", repository.Repo)
updateSecretString(secret, "username", repository.Username)
updateSecretString(secret, "password", repository.Password)
updateSecretString(secret, "bearerToken", repository.BearerToken)
updateSecretString(secret, "sshPrivateKey", repository.SSHPrivateKey)
updateSecretBool(secret, "enableOCI", repository.EnableOCI)
updateSecretString(secret, "tlsClientCertData", repository.TLSClientCertData)
@@ -404,6 +406,7 @@ func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*app
URL: string(secret.Data["url"]),
Username: string(secret.Data["username"]),
Password: string(secret.Data["password"]),
BearerToken: string(secret.Data["bearerToken"]),
SSHPrivateKey: string(secret.Data["sshPrivateKey"]),
TLSClientCertData: string(secret.Data["tlsClientCertData"]),
TLSClientCertKey: string(secret.Data["tlsClientCertKey"]),
@@ -456,6 +459,7 @@ func repoCredsToSecret(repoCreds *appsv1.RepoCreds, secret *corev1.Secret) {
updateSecretString(secret, "url", repoCreds.URL)
updateSecretString(secret, "username", repoCreds.Username)
updateSecretString(secret, "password", repoCreds.Password)
updateSecretString(secret, "bearerToken", repoCreds.BearerToken)
updateSecretString(secret, "sshPrivateKey", repoCreds.SSHPrivateKey)
updateSecretBool(secret, "enableOCI", repoCreds.EnableOCI)
updateSecretString(secret, "tlsClientCertData", repoCreds.TLSClientCertData)

View File

@@ -295,6 +295,9 @@ func newAuth(repoURL string, creds Creds) (transport.AuthMethod, error) {
}
return auth, nil
case HTTPSCreds:
if creds.bearerToken != "" {
return &githttp.TokenAuth{Token: creds.bearerToken}, nil
}
auth := githttp.BasicAuth{Username: creds.username, Password: creds.password}
if auth.Username == "" {
auth.Username = "x-access-token"
@@ -967,6 +970,8 @@ func (m *nativeGitClient) runCredentialedCmd(args ...string) error {
for _, e := range environ {
if strings.HasPrefix(e, forceBasicAuthHeaderEnv+"=") {
args = append([]string{"--config-env", "http.extraHeader=" + forceBasicAuthHeaderEnv}, args...)
} else if strings.HasPrefix(e, bearerAuthHeaderEnv+"=") {
args = append([]string{"--config-env", "http.extraHeader=" + bearerAuthHeaderEnv}, args...)
}
}

View File

@@ -1,6 +1,9 @@
package git
import (
"context"
"errors"
"io"
"os"
"os/exec"
"path"
@@ -9,6 +12,7 @@ import (
"testing"
"time"
"github.com/go-git/go-git/v5/plumbing/transport"
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -851,3 +855,142 @@ func Test_newAuth_AzureWorkloadIdentity(t *testing.T) {
_, ok := auth.(*githttp.TokenAuth)
require.Truef(t, ok, "expected TokenAuth but got %T", auth)
}
func TestNewAuth(t *testing.T) {
tests := []struct {
name string
repoURL string
creds Creds
expected transport.AuthMethod
wantErr bool
}{
{
name: "HTTPSCreds with bearer token",
repoURL: "https://github.com/org/repo.git",
creds: HTTPSCreds{
bearerToken: "test-token",
},
expected: &githttp.TokenAuth{Token: "test-token"},
wantErr: false,
},
{
name: "HTTPSCreds with basic auth",
repoURL: "https://github.com/org/repo.git",
creds: HTTPSCreds{
username: "test-user",
password: "test-password",
},
expected: &githttp.BasicAuth{Username: "test-user", Password: "test-password"},
wantErr: false,
},
{
name: "HTTPSCreds with basic auth no username",
repoURL: "https://github.com/org/repo.git",
creds: HTTPSCreds{
password: "test-password",
},
expected: &githttp.BasicAuth{Username: "x-access-token", Password: "test-password"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
auth, err := newAuth(tt.repoURL, tt.creds)
if (err != nil) != tt.wantErr {
t.Errorf("newAuth() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.Equal(t, tt.expected, auth)
})
}
}
func Test_nativeGitClient_runCredentialedCmd(t *testing.T) {
tests := []struct {
name string
creds Creds
environ []string
expectedArgs []string
expectedEnv []string
expectedErr bool
}{
{
name: "basic auth header set",
creds: &mockCreds{
environ: []string{forceBasicAuthHeaderEnv + "=Basic dGVzdDp0ZXN0"},
},
expectedArgs: []string{"--config-env", "http.extraHeader=" + forceBasicAuthHeaderEnv, "status"},
expectedEnv: []string{forceBasicAuthHeaderEnv + "=Basic dGVzdDp0ZXN0"},
expectedErr: false,
},
{
name: "bearer auth header set",
creds: &mockCreds{
environ: []string{bearerAuthHeaderEnv + "=Bearer test-token"},
},
expectedArgs: []string{"--config-env", "http.extraHeader=" + bearerAuthHeaderEnv, "status"},
expectedEnv: []string{bearerAuthHeaderEnv + "=Bearer test-token"},
expectedErr: false,
},
{
name: "no auth header set",
creds: &mockCreds{
environ: []string{},
},
expectedArgs: []string{"status"},
expectedEnv: []string{},
expectedErr: false,
},
{
name: "error getting environment",
creds: &mockCreds{
environErr: true,
},
expectedArgs: []string{},
expectedEnv: []string{},
expectedErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &nativeGitClient{
creds: tt.creds,
}
err := client.runCredentialedCmd("status")
if (err != nil) != tt.expectedErr {
t.Errorf("runCredentialedCmd() error = %v, expectedErr %v", err, tt.expectedErr)
return
}
if tt.expectedErr {
return
}
cmd := exec.Command("git", tt.expectedArgs...)
cmd.Env = append(os.Environ(), tt.expectedEnv...)
output, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("runCredentialedCmd() command error = %v, output = %s", err, output)
}
})
}
}
type mockCreds struct {
environ []string
environErr bool
}
func (m *mockCreds) Environ() (io.Closer, []string, error) {
if m.environErr {
return nil, nil, errors.New("error getting environment")
}
return io.NopCloser(nil), m.environ, nil
}
func (m *mockCreds) GetUserInfo(_ context.Context) (string, string, error) {
return "", "", nil
}

View File

@@ -49,6 +49,7 @@ const (
// githubAccessTokenUsername is a username that is used to with the github access token
githubAccessTokenUsername = "x-access-token"
forceBasicAuthHeaderEnv = "ARGOCD_GIT_AUTH_HEADER"
bearerAuthHeaderEnv = "ARGOCD_GIT_BEARER_AUTH_HEADER"
// This is the resource id of the OAuth application of Azure Devops.
azureDevopsEntraResourceId = "499b84ac-1321-427f-aa17-267ca6975798/.default"
)
@@ -133,6 +134,8 @@ type HTTPSCreds struct {
username string
// Password for authentication
password string
// Bearer token for authentication
bearerToken string
// Whether to ignore invalid server certificates
insecure bool
// Client certificate to use
@@ -149,10 +152,11 @@ type HTTPSCreds struct {
forceBasicAuth bool
}
func NewHTTPSCreds(username string, password string, clientCertData string, clientCertKey string, insecure bool, proxy string, noProxy string, store CredsStore, forceBasicAuth bool) GenericHTTPSCreds {
func NewHTTPSCreds(username string, password string, bearerToken string, clientCertData string, clientCertKey string, insecure bool, proxy string, noProxy string, store CredsStore, forceBasicAuth bool) GenericHTTPSCreds {
return HTTPSCreds{
username,
password,
bearerToken,
insecure,
clientCertData,
clientCertKey,
@@ -176,6 +180,11 @@ func (creds HTTPSCreds) BasicAuthHeader() string {
return h
}
func (creds HTTPSCreds) BearerAuthHeader() string {
h := "Authorization: Bearer " + creds.bearerToken
return h
}
// Get additional required environment variables for executing git client to
// access specific repository via HTTPS.
func (creds HTTPSCreds) Environ() (io.Closer, []string, error) {
@@ -237,6 +246,9 @@ func (creds HTTPSCreds) Environ() (io.Closer, []string, error) {
// skipped. This is insecure, but some environments may need it.
if creds.password != "" && creds.forceBasicAuth {
env = append(env, fmt.Sprintf("%s=%s", forceBasicAuthHeaderEnv, creds.BasicAuthHeader()))
} else if creds.bearerToken != "" {
// If bearer token is set, we will set ARGOCD_BEARER_AUTH_HEADER to hold the HTTP authorization header
env = append(env, fmt.Sprintf("%s=%s", bearerAuthHeaderEnv, creds.BearerAuthHeader()))
}
nonce := creds.store.Add(text.FirstNonEmpty(creds.username, githubAccessTokenUsername), creds.password)
env = append(env, creds.store.Environ(nonce)...)

View File

@@ -52,7 +52,7 @@ func (s *memoryCredsStore) Environ(_ string) []string {
func TestHTTPSCreds_Environ_no_cert_cleanup(t *testing.T) {
store := &memoryCredsStore{creds: make(map[string]cred)}
creds := NewHTTPSCreds("", "", "", "", true, "", "", store, false)
creds := NewHTTPSCreds("", "", "", "", "", true, "", "", store, false)
closer, _, err := creds.Environ()
require.NoError(t, err)
credsLenBefore := len(store.creds)
@@ -61,7 +61,7 @@ func TestHTTPSCreds_Environ_no_cert_cleanup(t *testing.T) {
}
func TestHTTPSCreds_Environ_insecure_true(t *testing.T) {
creds := NewHTTPSCreds("", "", "", "", true, "", "", &NoopCredsStore{}, false)
creds := NewHTTPSCreds("", "", "", "", "", true, "", "", &NoopCredsStore{}, false)
closer, env, err := creds.Environ()
t.Cleanup(func() {
io.Close(closer)
@@ -78,7 +78,7 @@ func TestHTTPSCreds_Environ_insecure_true(t *testing.T) {
}
func TestHTTPSCreds_Environ_insecure_false(t *testing.T) {
creds := NewHTTPSCreds("", "", "", "", false, "", "", &NoopCredsStore{}, false)
creds := NewHTTPSCreds("", "", "", "", "", false, "", "", &NoopCredsStore{}, false)
closer, env, err := creds.Environ()
t.Cleanup(func() {
io.Close(closer)
@@ -97,7 +97,7 @@ func TestHTTPSCreds_Environ_insecure_false(t *testing.T) {
func TestHTTPSCreds_Environ_forceBasicAuth(t *testing.T) {
t.Run("Enabled and credentials set", func(t *testing.T) {
store := &memoryCredsStore{creds: make(map[string]cred)}
creds := NewHTTPSCreds("username", "password", "", "", false, "", "", store, true)
creds := NewHTTPSCreds("username", "password", "", "", "", false, "", "", store, true)
closer, env, err := creds.Environ()
require.NoError(t, err)
defer closer.Close()
@@ -115,7 +115,7 @@ func TestHTTPSCreds_Environ_forceBasicAuth(t *testing.T) {
})
t.Run("Enabled but credentials not set", func(t *testing.T) {
store := &memoryCredsStore{creds: make(map[string]cred)}
creds := NewHTTPSCreds("", "", "", "", false, "", "", store, true)
creds := NewHTTPSCreds("", "", "", "", "", false, "", "", store, true)
closer, env, err := creds.Environ()
require.NoError(t, err)
defer closer.Close()
@@ -132,7 +132,7 @@ func TestHTTPSCreds_Environ_forceBasicAuth(t *testing.T) {
})
t.Run("Disabled with credentials set", func(t *testing.T) {
store := &memoryCredsStore{creds: make(map[string]cred)}
creds := NewHTTPSCreds("username", "password", "", "", false, "", "", store, false)
creds := NewHTTPSCreds("username", "password", "", "", "", false, "", "", store, false)
closer, env, err := creds.Environ()
require.NoError(t, err)
defer closer.Close()
@@ -150,7 +150,7 @@ func TestHTTPSCreds_Environ_forceBasicAuth(t *testing.T) {
t.Run("Disabled with credentials not set", func(t *testing.T) {
store := &memoryCredsStore{creds: make(map[string]cred)}
creds := NewHTTPSCreds("", "", "", "", false, "", "", store, false)
creds := NewHTTPSCreds("", "", "", "", "", false, "", "", store, false)
closer, env, err := creds.Environ()
require.NoError(t, err)
defer closer.Close()
@@ -167,9 +167,29 @@ func TestHTTPSCreds_Environ_forceBasicAuth(t *testing.T) {
})
}
func TestHTTPSCreds_Environ_bearerTokenAuth(t *testing.T) {
t.Run("Enabled and credentials set", func(t *testing.T) {
store := &memoryCredsStore{creds: make(map[string]cred)}
creds := NewHTTPSCreds("", "", "token", "", "", false, "", "", store, false)
closer, env, err := creds.Environ()
require.NoError(t, err)
defer closer.Close()
var header string
for _, envVar := range env {
if strings.HasPrefix(envVar, bearerAuthHeaderEnv+"=") {
header = envVar[len(bearerAuthHeaderEnv)+1:]
}
if header != "" {
break
}
}
assert.Equal(t, "Authorization: Bearer token", header)
})
}
func TestHTTPSCreds_Environ_clientCert(t *testing.T) {
store := &memoryCredsStore{creds: make(map[string]cred)}
creds := NewHTTPSCreds("", "", "clientCertData", "clientCertKey", false, "", "", store, false)
creds := NewHTTPSCreds("", "", "", "clientCertData", "clientCertKey", false, "", "", store, false)
closer, env, err := creds.Environ()
require.NoError(t, err)
var cert, key string

View File

@@ -134,7 +134,7 @@ func TestCustomHTTPClient(t *testing.T) {
assert.NotEqual(t, "", string(keyData))
// Get HTTPSCreds with client cert creds specified, and insecure connection
creds := NewHTTPSCreds("test", "test", string(certData), string(keyData), false, "http://proxy:5000", "", &NoopCredsStore{}, false)
creds := NewHTTPSCreds("test", "test", "", string(certData), string(keyData), false, "http://proxy:5000", "", &NoopCredsStore{}, false)
client := GetRepoHTTPClient("https://localhost:9443/foo/bar", false, creds, "http://proxy:5000", "")
assert.NotNil(t, client)
assert.NotNil(t, client.Transport)
@@ -163,7 +163,7 @@ func TestCustomHTTPClient(t *testing.T) {
t.Setenv("http_proxy", "http://proxy-from-env:7878")
// Get HTTPSCreds without client cert creds, but insecure connection
creds = NewHTTPSCreds("test", "test", "", "", true, "", "", &NoopCredsStore{}, false)
creds = NewHTTPSCreds("test", "test", "", "", "", true, "", "", &NoopCredsStore{}, false)
client = GetRepoHTTPClient("https://localhost:9443/foo/bar", true, creds, "", "")
assert.NotNil(t, client)
assert.NotNil(t, client.Transport)