mirror of
https://github.com/argoproj/argo-cd
synced 2026-05-24 09:50:08 +00:00
Settings were getting re-initialized when incomplete. Session manager now uses settings manager (#1000)
This commit is contained in:
parent
bc4c5d83ce
commit
09067585fa
9 changed files with 275 additions and 158 deletions
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
|||
84
server/account/account_test.go
Normal file
84
server/account/account_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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}}}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue