Settings were getting re-initialized when incomplete. Session manager now uses settings manager (#1000)

This commit is contained in:
Jesse Suen 2019-01-09 15:46:38 -08:00 committed by GitHub
parent bc4c5d83ce
commit 09067585fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 275 additions and 158 deletions

View file

@ -59,7 +59,6 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewGenDexConfigCommand())
command.AddCommand(NewImportCommand())
command.AddCommand(NewExportCommand())
command.AddCommand(NewSettingsCommand())
command.AddCommand(NewClusterConfig())
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
@ -348,42 +347,6 @@ func NewExportCommand() *cobra.Command {
return &command
}
// NewSettingsCommand returns a new instance of `argocd-util settings` command
func NewSettingsCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
updateSuperuser bool
superuserPassword string
updateSignature bool
)
var command = &cobra.Command{
Use: "settings",
Short: "Creates or updates Argo CD settings",
Long: "Creates or updates Argo CD settings",
Run: func(c *cobra.Command, args []string) {
conf, err := clientConfig.ClientConfig()
errors.CheckError(err)
namespace, wasSpecified, err := clientConfig.Namespace()
errors.CheckError(err)
if !(wasSpecified) {
namespace = "argocd"
}
kubeclientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, namespace)
_, err = settings.UpdateSettings(superuserPassword, settingsMgr, updateSignature, updateSuperuser, namespace)
errors.CheckError(err)
},
}
command.Flags().BoolVar(&updateSuperuser, "update-superuser", false, "force updating the superuser password")
command.Flags().StringVar(&superuserPassword, "superuser-password", "", "password for super user")
command.Flags().BoolVar(&updateSignature, "update-signature", false, "force updating the server-side token signing signature")
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}
// NewClusterConfig returns a new instance of `argocd-util cluster-kubeconfig` command
func NewClusterConfig() *cobra.Command {
var (

View file

@ -4,6 +4,7 @@ import (
"time"
jwt "github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -59,7 +60,7 @@ func (s *Server) UpdatePassword(ctx context.Context, q *UpdatePasswordRequest) (
if err != nil {
return nil, err
}
log.Infof("user '%s' updated password", username)
return &UpdatePasswordResponse{}, nil
}

View file

@ -0,0 +1,84 @@
package account
import (
"context"
"testing"
jwt "github.com/dgrijalva/jwt-go"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/server/session"
"github.com/argoproj/argo-cd/util/password"
sessionutil "github.com/argoproj/argo-cd/util/session"
"github.com/argoproj/argo-cd/util/settings"
)
const (
testNamespace = "default"
)
// return an AccountServer which returns fake data
func newTestAccountServer(ctx context.Context, objects ...runtime.Object) (*fake.Clientset, *Server, *session.Server) {
bcrypt, err := password.HashPassword("oldpassword")
errors.CheckError(err)
kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-cm",
Namespace: testNamespace,
},
}, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: testNamespace,
},
Data: map[string][]byte{
"admin.password": []byte(bcrypt),
"server.secretkey": []byte("test"),
},
})
settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
sessionMgr := sessionutil.NewSessionManager(settingsMgr)
return kubeclientset, NewServer(sessionMgr, settingsMgr), session.NewServer(sessionMgr)
}
func TestUpdatePassword(t *testing.T) {
ctx := context.Background()
_, accountServer, sessionServer := newTestAccountServer(ctx)
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
var err error
// ensure password is not allowed to be updated if given bad password
_, err = accountServer.UpdatePassword(ctx, &UpdatePasswordRequest{CurrentPassword: "badpassword", NewPassword: "newpassword"})
assert.Error(t, err)
assert.NoError(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "oldpassword"))
assert.Error(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "newpassword"))
// verify old password works
_, err = sessionServer.Create(ctx, &session.SessionCreateRequest{Username: "admin", Password: "oldpassword"})
assert.NoError(t, err)
// verify new password doesn't
_, err = sessionServer.Create(ctx, &session.SessionCreateRequest{Username: "admin", Password: "newpassword"})
assert.Error(t, err)
// ensure password can be updated with valid password and immediately be used
settings, err := accountServer.settingsMgr.GetSettings()
assert.NoError(t, err)
prevHash := settings.AdminPasswordHash
_, err = accountServer.UpdatePassword(ctx, &UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword"})
assert.NoError(t, err)
settings, err = accountServer.settingsMgr.GetSettings()
assert.NoError(t, err)
assert.NotEqual(t, prevHash, settings.AdminPasswordHash)
assert.NoError(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "newpassword"))
assert.Error(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "oldpassword"))
// verify old password is invalid
_, err = sessionServer.Create(ctx, &session.SessionCreateRequest{Username: "admin", Password: "oldpassword"})
assert.Error(t, err)
// verify new password works
_, err = sessionServer.Create(ctx, &session.SessionCreateRequest{Username: "admin", Password: "newpassword"})
assert.NoError(t, err)
}

View file

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
@ -24,15 +25,30 @@ import (
"github.com/argoproj/argo-cd/util/settings"
)
const testNamespace = "default"
func TestProjectServer(t *testing.T) {
enforcer := rbac.NewEnforcer(fake.NewSimpleClientset(), "default", common.ArgoCDRBACConfigMapName, nil)
kubeclientset := fake.NewSimpleClientset(&corev1.ConfigMap{
ObjectMeta: v1.ObjectMeta{Namespace: testNamespace, Name: "argocd-cm"},
}, &corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "argocd-secret",
Namespace: testNamespace,
},
Data: map[string][]byte{
"admin.password": []byte("test"),
"server.secretkey": []byte("test"),
},
})
settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
enforcer.SetBuiltinPolicy(test.BuiltinPolicy)
enforcer.SetDefaultRole("role:admin")
enforcer.SetClaimsEnforcerFunc(func(claims jwt.Claims, rvals ...interface{}) bool {
return true
})
existingProj := v1alpha1.AppProject{
ObjectMeta: v1.ObjectMeta{Name: "test", Namespace: "default"},
ObjectMeta: v1.ObjectMeta{Name: "test", Namespace: testNamespace},
Spec: v1alpha1.AppProjectSpec{
Destinations: []v1alpha1.ApplicationDestination{
{Namespace: "ns1", Server: "https://server1"},
@ -144,7 +160,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTokenSuccesfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(&settings.ArgoCDSettings{})
sessionMgr := session.NewSessionManager(settingsMgr)
projectWithRole := existingProj.DeepCopy()
tokenName := "testToken"
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
@ -163,7 +179,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestDeleteTokenSuccesfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(&settings.ArgoCDSettings{})
sessionMgr := session.NewSessionManager(settingsMgr)
projWithToken := existingProj.DeepCopy()
tokenName := "testToken"
issuedAt := int64(1)
@ -182,7 +198,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTwoTokensInRoleSuccess", func(t *testing.T) {
sessionMgr := session.NewSessionManager(&settings.ArgoCDSettings{})
sessionMgr := session.NewSessionManager(settingsMgr)
projWithToken := existingProj.DeepCopy()
tokenName := "testToken"
token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}

View file

@ -17,9 +17,9 @@ import (
"github.com/gobuffalo/packr"
golang_proto "github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/go-grpc-middleware/auth"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
log "github.com/sirupsen/logrus"
"github.com/soheilhy/cmux"
@ -36,7 +36,7 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"github.com/argoproj/argo-cd"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/controller"
"github.com/argoproj/argo-cd/errors"
@ -164,34 +164,14 @@ func initializeDefaultProject(opts ArgoCDServerOpts) error {
return err
}
// initializeSettings sets default secret settings (password set to hostname)
func initializeSettings(settingsMgr *settings_util.SettingsManager, opts ArgoCDServerOpts) (*settings_util.ArgoCDSettings, error) {
defaultPassword, err := os.Hostname()
errors.CheckError(err)
cdSettings, err := settings_util.UpdateSettings(defaultPassword, settingsMgr, false, false, opts.Namespace)
if err != nil {
// assume settings are initialized by another instance of api server
if apierrors.IsConflict(err) {
return settingsMgr.GetSettings()
} else {
log.Fatal(err)
}
}
return cdSettings, nil
}
// NewServer returns a new instance of the Argo CD API server
func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
settingsMgr := settings_util.NewSettingsManager(ctx, opts.KubeClientset, opts.Namespace)
settings, err := initializeSettings(settingsMgr, opts)
settings, err := settingsMgr.InitializeSettings()
errors.CheckError(err)
err = initializeDefaultProject(opts)
errors.CheckError(err)
sessionMgr := util_session.NewSessionManager(settings)
sessionMgr := util_session.NewSessionManager(settingsMgr)
factory := appinformer.NewFilteredSharedInformerFactory(opts.AppClientset, 0, opts.Namespace, func(options *metav1.ListOptions) {})
appInformer := factory.Argoproj().V1alpha1().Applications().Informer()

View file

@ -74,7 +74,10 @@ func fakeSecret(policy ...string) *apiv1.Secret {
Name: common.ArgoCDSecretName,
Namespace: fakeNamespace,
},
Data: make(map[string][]byte),
Data: map[string][]byte{
"admin.password": []byte("test"),
"server.secretkey": []byte("test"),
},
}
return &secret
}

View file

@ -10,7 +10,7 @@ import (
"strings"
"time"
"github.com/coreos/go-oidc"
oidc "github.com/coreos/go-oidc"
jwt "github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
@ -25,9 +25,9 @@ import (
// SessionManager generates and validates JWT tokens for login sessions.
type SessionManager struct {
settings *settings.ArgoCDSettings
client *http.Client
provider *oidc.Provider
settingsMgr *settings.SettingsManager
client *http.Client
provider *oidc.Provider
}
const (
@ -41,9 +41,13 @@ const (
)
// NewSessionManager creates a new session manager from Argo CD settings
func NewSessionManager(settings *settings.ArgoCDSettings) *SessionManager {
func NewSessionManager(settingsMgr *settings.SettingsManager) *SessionManager {
s := SessionManager{
settings: settings,
settingsMgr: settingsMgr,
}
settings, err := settingsMgr.GetSettings()
if err != nil {
panic(err)
}
tlsConfig := settings.TLSConfig()
if tlsConfig != nil {
@ -90,7 +94,11 @@ func (mgr *SessionManager) Create(subject string, secondsBeforeExpiry int64) (st
func (mgr *SessionManager) signClaims(claims jwt.Claims) (string, error) {
log.Infof("Issuing claims: %v", claims)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(mgr.settings.ServerSignature)
settings, err := mgr.settingsMgr.GetSettings()
if err != nil {
return "", err
}
return token.SignedString(settings.ServerSignature)
}
// Parse tries to parse the provided string and returns the token claims for local superuser login.
@ -100,19 +108,23 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
var claims jwt.MapClaims
settings, err := mgr.settingsMgr.GetSettings()
if err != nil {
return nil, err
}
token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return mgr.settings.ServerSignature, nil
return settings.ServerSignature, nil
})
if err != nil {
return nil, err
}
issuedAt := time.Unix(int64(claims["iat"].(float64)), 0)
if issuedAt.Before(mgr.settings.AdminPasswordMtime) {
if issuedAt.Before(settings.AdminPasswordMtime) {
return nil, fmt.Errorf("Password for superuser has changed since token issued")
}
return token.Claims, nil
@ -126,7 +138,11 @@ func (mgr *SessionManager) VerifyUsernamePassword(username, password string) err
if password == "" {
return status.Errorf(codes.Unauthenticated, blankPasswordError)
}
valid, _ := passwordutil.VerifyPassword(password, mgr.settings.AdminPasswordHash)
settings, err := mgr.settingsMgr.GetSettings()
if err != nil {
return err
}
valid, _ := passwordutil.VerifyPassword(password, settings.AdminPasswordHash)
if !valid {
return status.Errorf(codes.Unauthenticated, invalidLoginError)
}
@ -219,10 +235,14 @@ func (mgr *SessionManager) oidcProvider() (*oidc.Provider, error) {
// initializeOIDCProvider re-initializes the OIDC provider, querying the well known oidc
// configuration path (http://example-argocd.com/api/dex/.well-known/openid-configuration)
func (mgr *SessionManager) initializeOIDCProvider() (*oidc.Provider, error) {
if !mgr.settings.IsSSOConfigured() {
settings, err := mgr.settingsMgr.GetSettings()
if err != nil {
return nil, err
}
if !settings.IsSSOConfigured() {
return nil, fmt.Errorf("SSO is not configured")
}
provider, err := oidcutil.NewOIDCProvider(mgr.settings.IssuerURL(), mgr.client)
provider, err := oidcutil.NewOIDCProvider(settings.IssuerURL(), mgr.client)
if err != nil {
return nil, err
}

View file

@ -1,10 +1,18 @@
package session
package session_test
import (
"context"
"testing"
"github.com/argoproj/argo-cd/util/settings"
jwt "github.com/dgrijalva/jwt-go"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/util/password"
sessionutil "github.com/argoproj/argo-cd/util/session"
"github.com/argoproj/argo-cd/util/settings"
)
func TestSessionManager(t *testing.T) {
@ -12,10 +20,27 @@ func TestSessionManager(t *testing.T) {
defaultSecretKey = "Hello, world!"
defaultSubject = "argo"
)
set := settings.ArgoCDSettings{
ServerSignature: []byte(defaultSecretKey),
}
mgr := NewSessionManager(&set)
bcrypt, err := password.HashPassword("password")
errors.CheckError(err)
kubeclientset := fake.NewSimpleClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-cm",
Namespace: "argocd",
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "argocd",
},
Data: map[string][]byte{
"admin.password": []byte(bcrypt),
"server.secretkey": []byte(defaultSecretKey),
},
})
settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, "argocd")
mgr := sessionutil.NewSessionManager(settingsMgr)
token, err := mgr.Create(defaultSubject, 0)
if err != nil {

View file

@ -7,28 +7,27 @@ import (
"crypto/x509"
"encoding/base64"
"fmt"
"os"
"strings"
"sync"
"time"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/informers/core/v1"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
apiv1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
v1 "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
v1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/password"
tlsutil "github.com/argoproj/argo-cd/util/tls"
)
@ -144,7 +143,7 @@ func (e *incompleteSettingsError) Error() string {
}
func (mgr *SettingsManager) GetSecretsLister() (v1listers.SecretLister, error) {
err := mgr.ensureInitialized(false)
err := mgr.ensureSynced(false)
if err != nil {
return nil, err
}
@ -153,34 +152,35 @@ func (mgr *SettingsManager) GetSecretsLister() (v1listers.SecretLister, error) {
// GetSettings retrieves settings from the ArgoCDConfigMap and secret.
func (mgr *SettingsManager) GetSettings() (*ArgoCDSettings, error) {
err := mgr.ensureInitialized(false)
err := mgr.ensureSynced(false)
if err != nil {
return nil, err
}
var settings ArgoCDSettings
argoCDCM, err := mgr.configmaps.ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName)
if err != nil {
return nil, err
}
err = mgr.updateSettingsFromConfigMap(&settings, argoCDCM)
if err != nil {
return nil, err
}
argoCDSecret, err := mgr.secrets.Secrets(mgr.namespace).Get(common.ArgoCDSecretName)
if err != nil {
return &settings, err
return nil, err
}
err = updateSettingsFromSecret(&settings, argoCDSecret)
if err != nil {
return &settings, err
var settings ArgoCDSettings
var errs []error
if err := updateSettingsFromConfigMap(&settings, argoCDCM); err != nil {
errs = append(errs, err)
}
if err := updateSettingsFromSecret(&settings, argoCDSecret); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return &settings, errs[0]
}
return &settings, nil
}
// MigrateLegacyRepoSettings migrates legacy (v0.10 and below) repo secrets into the v0.11 configmap
func (mgr *SettingsManager) MigrateLegacyRepoSettings(settings *ArgoCDSettings) error {
err := mgr.ensureInitialized(false)
err := mgr.ensureSynced(false)
if err != nil {
return err
}
@ -247,7 +247,7 @@ func (mgr *SettingsManager) initialize(ctx context.Context) error {
if !cache.WaitForCacheSync(ctx.Done(), cmInformer.HasSynced, secretsInformer.HasSynced) {
return fmt.Errorf("Timed out waiting for settings cache to sync")
}
log.Info("Configmap/secret informer synched")
log.Info("Configmap/secret informer synced")
tryNotify := func() {
newSettings, err := mgr.GetSettings()
@ -282,7 +282,7 @@ func (mgr *SettingsManager) initialize(ctx context.Context) error {
return nil
}
func (mgr *SettingsManager) ensureInitialized(forceResync bool) error {
func (mgr *SettingsManager) ensureSynced(forceResync bool) error {
if !forceResync && mgr.secrets != nil && mgr.configmaps != nil {
return nil
}
@ -300,49 +300,59 @@ func (mgr *SettingsManager) ensureInitialized(forceResync bool) error {
return mgr.initialize(ctx)
}
func (mgr *SettingsManager) updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.ConfigMap) error {
func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.ConfigMap) error {
settings.DexConfig = argoCDCM.Data[settingDexConfigKey]
settings.OIDCConfigRAW = argoCDCM.Data[settingsOIDCConfigKey]
settings.URL = argoCDCM.Data[settingURLKey]
settings.AppInstanceLabelKey = argoCDCM.Data[settingsApplicationInstanceLabelKey]
repositoriesStr := argoCDCM.Data[repositoriesKey]
var errors []error
if repositoriesStr != "" {
settings.Repositories = make([]RepoCredentials, 0)
err := yaml.Unmarshal([]byte(repositoriesStr), &settings.Repositories)
repositories := make([]RepoCredentials, 0)
err := yaml.Unmarshal([]byte(repositoriesStr), &repositories)
if err != nil {
return err
errors = append(errors, err)
} else {
settings.Repositories = repositories
}
}
helmRepositoriesStr := argoCDCM.Data[helmRepositoriesKey]
if helmRepositoriesStr != "" {
settings.HelmRepositories = make([]HelmRepoCredentials, 0)
err := yaml.Unmarshal([]byte(helmRepositoriesStr), &settings.HelmRepositories)
helmRepositories := make([]HelmRepoCredentials, 0)
err := yaml.Unmarshal([]byte(helmRepositoriesStr), &helmRepositories)
if err != nil {
return err
errors = append(errors, err)
} else {
settings.HelmRepositories = helmRepositories
}
}
settings.AppInstanceLabelKey = argoCDCM.Data[settingsApplicationInstanceLabelKey]
if len(errors) > 0 {
return errors[0]
}
return nil
}
// updateSettingsFromSecret transfers settings from a Kubernetes secret into an ArgoCDSettings struct.
func updateSettingsFromSecret(settings *ArgoCDSettings, argoCDSecret *apiv1.Secret) error {
var errs []error
adminPasswordHash, ok := argoCDSecret.Data[settingAdminPasswordHashKey]
if !ok {
return &incompleteSettingsError{message: "admin.password is missing"}
if ok {
settings.AdminPasswordHash = string(adminPasswordHash)
} else {
errs = append(errs, &incompleteSettingsError{message: "admin.password is missing"})
}
settings.AdminPasswordHash = string(adminPasswordHash)
settings.AdminPasswordMtime = time.Now().UTC()
if adminPasswordMtimeBytes, ok := argoCDSecret.Data[settingAdminPasswordMtimeKey]; ok {
adminPasswordMtimeBytes, ok := argoCDSecret.Data[settingAdminPasswordMtimeKey]
if ok {
if adminPasswordMtime, err := time.Parse(time.RFC3339, string(adminPasswordMtimeBytes)); err == nil {
settings.AdminPasswordMtime = adminPasswordMtime
}
}
secretKey, ok := argoCDSecret.Data[settingServerSignatureKey]
if !ok {
return &incompleteSettingsError{message: "server.secretkey is missing"}
if ok {
settings.ServerSignature = secretKey
} else {
errs = append(errs, &incompleteSettingsError{message: "server.secretkey is missing"})
}
settings.ServerSignature = secretKey
if githubWebhookSecret := argoCDSecret.Data[settingsWebhookGitHubSecretKey]; len(githubWebhookSecret) > 0 {
settings.WebhookGitHubSecret = string(githubWebhookSecret)
}
@ -358,21 +368,25 @@ func updateSettingsFromSecret(settings *ArgoCDSettings, argoCDSecret *apiv1.Secr
if certOk && keyOk {
cert, err := tls.X509KeyPair(serverCert, serverKey)
if err != nil {
return &incompleteSettingsError{message: fmt.Sprintf("invalid x509 key pair %s/%s in secret: %s", settingServerCertificate, settingServerPrivateKey, err)}
errs = append(errs, &incompleteSettingsError{message: fmt.Sprintf("invalid x509 key pair %s/%s in secret: %s", settingServerCertificate, settingServerPrivateKey, err)})
} else {
settings.Certificate = &cert
}
settings.Certificate = &cert
}
secretValues := make(map[string]string, len(argoCDSecret.Data))
for k, v := range argoCDSecret.Data {
secretValues[k] = string(v)
}
settings.Secrets = secretValues
if len(errs) > 0 {
return errs[0]
}
return nil
}
// SaveSettings serializes ArgoCDSettings and upserts it into K8s secret/configmap
func (mgr *SettingsManager) SaveSettings(settings *ArgoCDSettings) error {
err := mgr.ensureInitialized(false)
err := mgr.ensureSynced(false)
if err != nil {
return err
}
@ -447,24 +461,26 @@ func (mgr *SettingsManager) SaveSettings(settings *ArgoCDSettings) error {
}
createSecret = true
}
if argoCDSecret.Data == nil {
argoCDSecret.Data = make(map[string][]byte)
}
argoCDSecret.StringData = make(map[string]string)
argoCDSecret.StringData[settingServerSignatureKey] = string(settings.ServerSignature)
argoCDSecret.StringData[settingAdminPasswordHashKey] = settings.AdminPasswordHash
argoCDSecret.StringData[settingAdminPasswordMtimeKey] = settings.AdminPasswordMtime.Format(time.RFC3339)
argoCDSecret.Data[settingServerSignatureKey] = settings.ServerSignature
argoCDSecret.Data[settingAdminPasswordHashKey] = []byte(settings.AdminPasswordHash)
argoCDSecret.Data[settingAdminPasswordMtimeKey] = []byte(settings.AdminPasswordMtime.Format(time.RFC3339))
if settings.WebhookGitHubSecret != "" {
argoCDSecret.StringData[settingsWebhookGitHubSecretKey] = settings.WebhookGitHubSecret
argoCDSecret.Data[settingsWebhookGitHubSecretKey] = []byte(settings.WebhookGitHubSecret)
}
if settings.WebhookGitLabSecret != "" {
argoCDSecret.StringData[settingsWebhookGitLabSecretKey] = settings.WebhookGitLabSecret
argoCDSecret.Data[settingsWebhookGitLabSecretKey] = []byte(settings.WebhookGitLabSecret)
}
if settings.WebhookBitbucketUUID != "" {
argoCDSecret.StringData[settingsWebhookBitbucketUUIDKey] = settings.WebhookBitbucketUUID
argoCDSecret.Data[settingsWebhookBitbucketUUIDKey] = []byte(settings.WebhookBitbucketUUID)
}
if settings.Certificate != nil {
cert, key := tlsutil.EncodeX509KeyPairString(*settings.Certificate)
argoCDSecret.StringData[settingServerCertificate] = cert
argoCDSecret.StringData[settingServerPrivateKey] = key
cert, key := tlsutil.EncodeX509KeyPair(*settings.Certificate)
argoCDSecret.Data[settingServerCertificate] = cert
argoCDSecret.Data[settingServerPrivateKey] = key
} else {
delete(argoCDSecret.Data, settingServerCertificate)
delete(argoCDSecret.Data, settingServerPrivateKey)
@ -494,7 +510,7 @@ func NewSettingsManager(ctx context.Context, clientset kubernetes.Interface, nam
}
func (mgr *SettingsManager) ResyncInformers() error {
return mgr.ensureInitialized(true)
return mgr.ensureSynced(true)
}
// IsSSOConfigured returns whether or not single-sign-on is configured
@ -636,38 +652,40 @@ func isIncompleteSettingsError(err error) bool {
return ok
}
// UpdateSettings is used to update the admin password, signature, certificate etc
func UpdateSettings(defaultPassword string, settingsMgr *SettingsManager, updateSignature bool, updateSuperuser bool, Namespace string) (*ArgoCDSettings, error) {
cdSettings, err := settingsMgr.GetSettings()
if err != nil && !apierr.IsNotFound(err) && !isIncompleteSettingsError(err) {
// InitializeSettings is used to initialize empty admin password, signature, certificate etc if missing
func (mgr *SettingsManager) InitializeSettings() (*ArgoCDSettings, error) {
cdSettings, err := mgr.GetSettings()
if err != nil && !isIncompleteSettingsError(err) {
return nil, err
}
if cdSettings == nil {
cdSettings = &ArgoCDSettings{}
}
if cdSettings.ServerSignature == nil || updateSignature {
if cdSettings.ServerSignature == nil {
// set JWT signature
signature, err := util.MakeSignature(32)
if err != nil {
return nil, err
}
cdSettings.ServerSignature = signature
log.Info("Initialized server signature")
}
if cdSettings.AdminPasswordHash == "" || updateSuperuser {
passwordRaw := defaultPassword
if passwordRaw == "" {
passwordRaw, err = cli.ReadAndConfirmPassword()
if err != nil {
return nil, err
}
if cdSettings.AdminPasswordHash == "" {
defaultPassword, err := os.Hostname()
if err != nil {
return nil, err
}
hashedPassword, err := password.HashPassword(passwordRaw)
hashedPassword, err := password.HashPassword(defaultPassword)
if err != nil {
return nil, err
}
cdSettings.AdminPasswordHash = hashedPassword
cdSettings.AdminPasswordMtime = time.Now().UTC()
log.Info("Initialized admin password")
}
if cdSettings.AdminPasswordMtime.IsZero() {
cdSettings.AdminPasswordMtime = time.Now().UTC()
log.Info("Initialized admin mtime")
}
if cdSettings.Certificate == nil {
@ -675,9 +693,9 @@ func UpdateSettings(defaultPassword string, settingsMgr *SettingsManager, update
hosts := []string{
"localhost",
"argocd-server",
fmt.Sprintf("argocd-server.%s", Namespace),
fmt.Sprintf("argocd-server.%s.svc", Namespace),
fmt.Sprintf("argocd-server.%s.svc.cluster.local", Namespace),
fmt.Sprintf("argocd-server.%s", mgr.namespace),
fmt.Sprintf("argocd-server.%s.svc", mgr.namespace),
fmt.Sprintf("argocd-server.%s.svc.cluster.local", mgr.namespace),
}
certOpts := tlsutil.CertOptions{
Hosts: hosts,
@ -689,16 +707,23 @@ func UpdateSettings(defaultPassword string, settingsMgr *SettingsManager, update
return nil, err
}
cdSettings.Certificate = cert
log.Info("Initialized TLS certificate")
}
if len(cdSettings.Repositories) == 0 {
err = settingsMgr.MigrateLegacyRepoSettings(cdSettings)
err = mgr.MigrateLegacyRepoSettings(cdSettings)
if err != nil {
return nil, err
}
}
return cdSettings, settingsMgr.SaveSettings(cdSettings)
err = mgr.SaveSettings(cdSettings)
if apierrors.IsConflict(err) {
// assume settings are initialized by another instance of api server
log.Warnf("conflict when initializing settings. assuming updated by another replica")
return mgr.GetSettings()
}
return cdSettings, nil
}
// ReplaceStringSecret checks if given string is a secret key reference ( starts with $ ) and returns corresponding value from provided map