Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add bearer token auth #21462

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
20 changes: 20 additions & 0 deletions assets/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion cmd/argocd/commands/admin/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 repository via HTTPS using bearer token:
argocd admin repo generate-spec https://git.example.com/repos/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

Expand Down Expand Up @@ -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",
Expand All @@ -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{})
Expand Down
10 changes: 9 additions & 1 deletion cmd/argocd/commands/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand All @@ -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{
Expand Down
9 changes: 9 additions & 0 deletions cmd/argocd/commands/repocreds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -165,6 +166,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,
Expand All @@ -177,6 +185,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)")
Expand Down
33 changes: 33 additions & 0 deletions cmd/util/common.go
Original file line number Diff line number Diff line change
@@ -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 changes: 155 additions & 0 deletions cmd/util/common_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
1 change: 1 addition & 0 deletions cmd/util/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 repository")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we change it to bearer token to the repository, so it will be consistent with username and password?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is on purpose, since It is only relevant for Git, at least at the moment. When support for Helm would be added, this would be a good time to make it consistent.

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)")
Expand Down
4 changes: 4 additions & 0 deletions docs/user-guide/commands/argocd_admin_repo_generate-spec.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/user-guide/commands/argocd_repo_add.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/user-guide/commands/argocd_repocreds_add.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading