diff --git a/cmd/argocd-server/commands/root.go b/cmd/argocd-server/commands/root.go index c1978c2c850bc..f181ec764a4b5 100644 --- a/cmd/argocd-server/commands/root.go +++ b/cmd/argocd-server/commands/root.go @@ -19,7 +19,6 @@ func NewCommand() *cobra.Command { clientConfig clientcmd.ClientConfig staticAssetsDir string repoServerAddress string - configMapName string ) var command = &cobra.Command{ Use: cliName, @@ -40,7 +39,7 @@ func NewCommand() *cobra.Command { appclientset := appclientset.NewForConfigOrDie(config) repoclientset := reposerver.NewRepositoryServerClientset(repoServerAddress) - argocd := server.NewServer(kubeclientset, appclientset, repoclientset, namespace, staticAssetsDir, configMapName) + argocd := server.NewServer(kubeclientset, appclientset, repoclientset, namespace, staticAssetsDir) argocd.Run() }, } @@ -49,7 +48,6 @@ func NewCommand() *cobra.Command { command.Flags().StringVar(&staticAssetsDir, "staticassets", "", "Static assets directory path") command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error") command.Flags().StringVar(&repoServerAddress, "repo-server", "localhost:8081", "Repo server address.") - command.Flags().StringVar(&configMapName, "config-map", "", "Name of a Kubernetes config map to use.") command.AddCommand(cli.NewVersionCmd(cliName)) return command } diff --git a/cmd/argocd/commands/install.go b/cmd/argocd/commands/install.go index ef055f3fcc5e7..1ea8da0729a59 100644 --- a/cmd/argocd/commands/install.go +++ b/cmd/argocd/commands/install.go @@ -21,6 +21,11 @@ func NewInstallCommand() *cobra.Command { Run: func(c *cobra.Command, args []string) { conf, err := clientConfig.ClientConfig() errors.CheckError(err) + namespace, wasSpecified, err := clientConfig.Namespace() + errors.CheckError(err) + if wasSpecified { + installOpts.Namespace = namespace + } installer, err := install.NewInstaller(conf, installOpts) errors.CheckError(err) installer.Install() @@ -31,7 +36,6 @@ func NewInstallCommand() *cobra.Command { command.Flags().BoolVar(&installOpts.ConfigSuperuser, "config-superuser", false, "create or update a superuser username and password") command.Flags().BoolVar(&installOpts.CreateSignature, "create-signature", false, "create or update the server-side token signing signature") command.Flags().StringVar(&installOpts.ConfigMap, "config-map", "", "apply settings from a Kubernetes config map") - command.Flags().StringVar(&installOpts.Namespace, "install-namespace", install.DefaultInstallNamespace, "install into a specific namespace") command.Flags().StringVar(&installOpts.ControllerImage, "controller-image", install.DefaultControllerImage, "use a specified controller image") command.Flags().StringVar(&installOpts.ServerImage, "server-image", install.DefaultServerImage, "use a specified api server image") command.Flags().StringVar(&installOpts.UIImage, "ui-image", install.DefaultUIImage, "use a specified ui image") diff --git a/cmd/argocd/commands/uninstall.go b/cmd/argocd/commands/uninstall.go index fa6cd6e21c232..62d5810c9fff9 100644 --- a/cmd/argocd/commands/uninstall.go +++ b/cmd/argocd/commands/uninstall.go @@ -21,12 +21,16 @@ func NewUninstallCommand() *cobra.Command { Run: func(c *cobra.Command, args []string) { conf, err := clientConfig.ClientConfig() errors.CheckError(err) + namespace, wasSpecified, err := clientConfig.Namespace() + errors.CheckError(err) + if wasSpecified { + installOpts.Namespace = namespace + } installer, err := install.NewInstaller(conf, installOpts) errors.CheckError(err) installer.Uninstall() }, } - command.Flags().StringVar(&installOpts.Namespace, "install-namespace", install.DefaultInstallNamespace, "uninstall from a specific namespace") clientConfig = cli.AddKubectlFlagsToCmd(command) return command } diff --git a/common/common.go b/common/common.go index cdebf67f30551..f58aa28094474 100644 --- a/common/common.go +++ b/common/common.go @@ -16,6 +16,12 @@ const ( SecretTypeCluster = "cluster" ) +const ( + ArgoCDAdminUsername = "admin" + ArgoCDSecretName = "argocd-secret" + ArgoCDConfigMapName = "argocd-cm" +) + var ( // LabelKeyAppInstance refers to the application instance resource name LabelKeyAppInstance = MetadataPrefix + "/app-instance" @@ -25,6 +31,7 @@ var ( // LabelKeyApplicationControllerInstanceID is the label which allows to separate application among multiple running application controllers. LabelKeyApplicationControllerInstanceID = application.ApplicationFullName + "/controller-instanceid" + // LabelApplicationName is the label which indicates that resource belongs to application with the specified name LabelApplicationName = application.ApplicationFullName + "/app-name" ) @@ -44,46 +51,3 @@ var ArgoCDManagerPolicyRules = []rbacv1.PolicyRule{ Verbs: []string{"*"}, }, } - -const ( - ArgoCDServerServiceAccount = "argocd-server" - ArgoCDServerRole = "argocd-server-role" - ArgoCDServerRoleBinding = "argocd-server-role-binding" -) - -var ArgoCDServerPolicyRules = []rbacv1.PolicyRule{ - { - APIGroups: []string{""}, - Resources: []string{"pods", "pods/exec", "pods/log"}, - Verbs: []string{"get", "list", "watch"}, - }, - { - APIGroups: []string{""}, - Resources: []string{"secrets"}, - Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, - }, - { - APIGroups: []string{"argoproj.io"}, - Resources: []string{"applications"}, - Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, - }, -} - -const ( - ApplicationControllerServiceAccount = "application-controller" - ApplicationControllerRole = "application-controller-role" - ApplicationControllerRoleBinding = "application-controller-role-binding" -) - -var ApplicationControllerPolicyRules = []rbacv1.PolicyRule{ - { - APIGroups: []string{""}, - Resources: []string{"secrets"}, - Verbs: []string{"get"}, - }, - { - APIGroups: []string{"argoproj.io"}, - Resources: []string{"applications"}, - Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, - }, -} diff --git a/install/install.go b/install/install.go index 60b1d7cc6c757..1f0ac2861bedb 100644 --- a/install/install.go +++ b/install/install.go @@ -1,25 +1,24 @@ package install import ( - "bufio" "fmt" - "log" - "os" - "strconv" "strings" "syscall" + "github.com/argoproj/argo-cd/common" "github.com/argoproj/argo-cd/errors" - "github.com/argoproj/argo-cd/util" + "github.com/argoproj/argo-cd/util/config" "github.com/argoproj/argo-cd/util/diff" "github.com/argoproj/argo-cd/util/kube" + "github.com/argoproj/argo-cd/util/password" + "github.com/argoproj/argo-cd/util/session" "github.com/ghodss/yaml" "github.com/gobuffalo/packr" + log "github.com/sirupsen/logrus" "github.com/yudai/gojsondiff/formatter" "golang.org/x/crypto/ssh/terminal" appsv1beta2 "k8s.io/api/apps/v1beta2" apiv1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apierr "k8s.io/apimachinery/pkg/api/errors" @@ -75,6 +74,9 @@ func NewInstaller(config *rest.Config, opts InstallOptions) (*Installer, error) box: packr.NewBox("./manifests"), config: &shallowCopy, } + if opts.Namespace == "" { + inst.Namespace = DefaultInstallNamespace + } var err error inst.dynClientPool = dynamic.NewDynamicClientPool(inst.config) inst.disco, err = discovery.NewDiscoveryClientForConfig(inst.config) @@ -87,6 +89,7 @@ func NewInstaller(config *rest.Config, opts InstallOptions) (*Installer, error) func (i *Installer) Install() { i.InstallNamespace() i.InstallApplicationCRD() + i.InstallSettings() i.InstallApplicationController() i.InstallArgoCDServer() i.InstallArgoCDRepoServer() @@ -105,12 +108,6 @@ func (i *Installer) Uninstall() { i.MustUninstallResource(&obj) } } - - // i.InstallNamespace() - // i.InstallApplicationCRD() - // i.InstallApplicationController() - // i.InstallArgoCDServer() - // i.InstallArgoCDRepoServer() } func (i *Installer) InstallNamespace() { @@ -130,15 +127,61 @@ func (i *Installer) InstallApplicationCRD() { i.MustInstallResource(kube.MustToUnstructured(&applicationCRD)) } +func (i *Installer) InstallSettings() { + kubeclientset, err := kubernetes.NewForConfig(i.config) + errors.CheckError(err) + configManager := config.NewConfigManager(kubeclientset, i.Namespace) + _, err = configManager.GetSettings() + if err != nil { + if !apierr.IsNotFound(err) { + log.Fatal(err) + } + // configmap/secret not yet created + signature, err := session.MakeSignature(32) + errors.CheckError(err) + passwordRaw := readAndConfirmPassword() + hashedPassword, err := password.HashPassword(passwordRaw) + errors.CheckError(err) + newSettings := config.ArgoCDSettings{ + ServerSignature: signature, + LocalUsers: map[string]string{ + common.ArgoCDAdminUsername: hashedPassword, + }, + } + err = configManager.SaveSettings(&newSettings) + errors.CheckError(err) + } else { + log.Infof("Settings already exists. Skipping creation") + } +} + +func readAndConfirmPassword() string { + for { + fmt.Print("*** Enter an admin password: ") + password, err := terminal.ReadPassword(syscall.Stdin) + errors.CheckError(err) + fmt.Print("\n") + fmt.Print("*** Confirm the admin password: ") + confirmPassword, err := terminal.ReadPassword(syscall.Stdin) + errors.CheckError(err) + fmt.Print("\n") + if string(password) == string(confirmPassword) { + return string(password) + } + log.Error("Passwords do not match") + } +} + func (i *Installer) InstallApplicationController() { var applicationControllerServiceAccount apiv1.ServiceAccount var applicationControllerRole rbacv1.Role var applicationControllerRoleBinding rbacv1.RoleBinding var applicationControllerDeployment appsv1beta2.Deployment - i.unmarshalManifest("02a_application-controller-sa.yaml", &applicationControllerServiceAccount) - i.unmarshalManifest("02b_application-controller-role.yaml", &applicationControllerRole) - i.unmarshalManifest("02c_application-controller-rolebinding.yaml", &applicationControllerRoleBinding) - i.unmarshalManifest("02d_application-controller-deployment.yaml", &applicationControllerDeployment) + i.unmarshalManifest("03a_application-controller-sa.yaml", &applicationControllerServiceAccount) + i.unmarshalManifest("03b_application-controller-role.yaml", &applicationControllerRole) + i.unmarshalManifest("03c_application-controller-rolebinding.yaml", &applicationControllerRoleBinding) + i.unmarshalManifest("03d_application-controller-deployment.yaml", &applicationControllerDeployment) + applicationControllerRoleBinding.Subjects[0].Namespace = i.Namespace applicationControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ControllerImage applicationControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy) i.MustInstallResource(kube.MustToUnstructured(&applicationControllerServiceAccount)) @@ -153,68 +196,29 @@ func (i *Installer) InstallArgoCDServer() { var argoCDServerControllerRoleBinding rbacv1.RoleBinding var argoCDServerControllerDeployment appsv1beta2.Deployment var argoCDServerService apiv1.Service - i.unmarshalManifest("03a_argocd-server-sa.yaml", &argoCDServerServiceAccount) - i.unmarshalManifest("03b_argocd-server-role.yaml", &argoCDServerControllerRole) - i.unmarshalManifest("03c_argocd-server-rolebinding.yaml", &argoCDServerControllerRoleBinding) - i.unmarshalManifest("03d_argocd-server-deployment.yaml", &argoCDServerControllerDeployment) - i.unmarshalManifest("03e_argocd-server-service.yaml", &argoCDServerService) + i.unmarshalManifest("04a_argocd-server-sa.yaml", &argoCDServerServiceAccount) + i.unmarshalManifest("04b_argocd-server-role.yaml", &argoCDServerControllerRole) + i.unmarshalManifest("04c_argocd-server-rolebinding.yaml", &argoCDServerControllerRoleBinding) + i.unmarshalManifest("04d_argocd-server-deployment.yaml", &argoCDServerControllerDeployment) + i.unmarshalManifest("04e_argocd-server-service.yaml", &argoCDServerService) + argoCDServerControllerRoleBinding.Subjects[0].Namespace = i.Namespace argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].Image = i.UIImage argoCDServerControllerDeployment.Spec.Template.Spec.InitContainers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy) argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.ServerImage argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy) - - kubeclientset, err := kubernetes.NewForConfig(i.config) - errors.CheckError(err) - - configManager := util.NewConfigManager(kubeclientset, i.Namespace, i.ConfigMap) - errors.CheckError(err) - - if i.InstallOptions.ConfigMap != "" { - quotedConfigMapName := strconv.Quote(i.InstallOptions.ConfigMap) - container := &argoCDServerControllerDeployment.Spec.Template.Spec.Containers[0] - container.Command = append(container.Command, "--config-map", quotedConfigMapName) - } - i.MustInstallResource(kube.MustToUnstructured(&argoCDServerServiceAccount)) i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRole)) i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerRoleBinding)) i.MustInstallResource(kube.MustToUnstructured(&argoCDServerControllerDeployment)) i.MustInstallResource(kube.MustToUnstructured(&argoCDServerService)) - // Ignore errors because settings aren't fully formed - settings, _ := configManager.GetSettings() - - // Generate a new superuser on command or if there are no superusers. - if i.InstallOptions.ConfigSuperuser || len(settings.LocalUsers) == 0 { - inputReader := bufio.NewReader(os.Stdin) - - fmt.Print("*** Please enter a superuser username: ") - rootUsername, err := inputReader.ReadString('\n') - errors.CheckError(err) - rootUsername = strings.Trim(rootUsername, "\n") - - fmt.Print("*** Please enter a superuser password: ") - rawPassword, err := terminal.ReadPassword(syscall.Stdin) - errors.CheckError(err) - fmt.Print("\n") - - err = configManager.SetRootUserCredentials(rootUsername, string(rawPassword)) - errors.CheckError(err) - } - - // Generate a new secret key on command or if the server signature isn't set. - // This has the side effect of invalidating all current login sessions. - if i.InstallOptions.CreateSignature || len(settings.ServerSignature) == 0 { - err = configManager.GenerateServerSignature() - errors.CheckError(err) - } } func (i *Installer) InstallArgoCDRepoServer() { var argoCDRepoServerControllerDeployment appsv1beta2.Deployment var argoCDRepoServerService apiv1.Service - i.unmarshalManifest("04a_argocd-repo-server-deployment.yaml", &argoCDRepoServerControllerDeployment) - i.unmarshalManifest("04b_argocd-repo-server-service.yaml", &argoCDRepoServerService) + i.unmarshalManifest("05a_argocd-repo-server-deployment.yaml", &argoCDRepoServerControllerDeployment) + i.unmarshalManifest("05b_argocd-repo-server-service.yaml", &argoCDRepoServerService) argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].Image = i.RepoServerImage argoCDRepoServerControllerDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = apiv1.PullPolicy(i.ImagePullPolicy) i.MustInstallResource(kube.MustToUnstructured(&argoCDRepoServerControllerDeployment)) diff --git a/install/manifests/02a_argocd-cm.yaml b/install/manifests/02a_argocd-cm.yaml new file mode 100644 index 0000000000000..fef373c61cbb0 --- /dev/null +++ b/install/manifests/02a_argocd-cm.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: argocd-cm + namespace: argocd +# TODO: future argocd tuning keys go here (e.g. resync period) +data: {} \ No newline at end of file diff --git a/install/manifests/02b_argocd-secret.yaml b/install/manifests/02b_argocd-secret.yaml new file mode 100644 index 0000000000000..de744e367b85a --- /dev/null +++ b/install/manifests/02b_argocd-secret.yaml @@ -0,0 +1,13 @@ +# NOTE: the values in this secret are provided as working manifest example and are not the values +# used during an install. New values will be generated as part of `argocd install` +apiVersion: v1 +kind: Secret +metadata: + name: argocd-secret + namespace: argocd +type: Opaque +data: + # bcrypt hash of 'password' + admin.password: JDJhJDEwJGVYYkZmOEt3NUMzTDJVbE9FRDNqUU9QMC5reVNBamVLUXY0N3NqaFFpWlZwTkkyU2dMTzd1 + # random server signature key for session validation + server.secretkey: aEDvv73vv70F77+9CRBSNu+/vTYQ77+9EUFh77+9LzFyJ++/vXfLsO+/vWRbeu+/ve+/vQ== diff --git a/install/manifests/02a_application-controller-sa.yaml b/install/manifests/03a_application-controller-sa.yaml similarity index 100% rename from install/manifests/02a_application-controller-sa.yaml rename to install/manifests/03a_application-controller-sa.yaml diff --git a/install/manifests/02b_application-controller-role.yaml b/install/manifests/03b_application-controller-role.yaml similarity index 100% rename from install/manifests/02b_application-controller-role.yaml rename to install/manifests/03b_application-controller-role.yaml diff --git a/install/manifests/02c_application-controller-rolebinding.yaml b/install/manifests/03c_application-controller-rolebinding.yaml similarity index 100% rename from install/manifests/02c_application-controller-rolebinding.yaml rename to install/manifests/03c_application-controller-rolebinding.yaml diff --git a/install/manifests/02d_application-controller-deployment.yaml b/install/manifests/03d_application-controller-deployment.yaml similarity index 100% rename from install/manifests/02d_application-controller-deployment.yaml rename to install/manifests/03d_application-controller-deployment.yaml diff --git a/install/manifests/03a_argocd-server-sa.yaml b/install/manifests/04a_argocd-server-sa.yaml similarity index 100% rename from install/manifests/03a_argocd-server-sa.yaml rename to install/manifests/04a_argocd-server-sa.yaml diff --git a/install/manifests/03b_argocd-server-role.yaml b/install/manifests/04b_argocd-server-role.yaml similarity index 100% rename from install/manifests/03b_argocd-server-role.yaml rename to install/manifests/04b_argocd-server-role.yaml diff --git a/install/manifests/03c_argocd-server-rolebinding.yaml b/install/manifests/04c_argocd-server-rolebinding.yaml similarity index 100% rename from install/manifests/03c_argocd-server-rolebinding.yaml rename to install/manifests/04c_argocd-server-rolebinding.yaml diff --git a/install/manifests/03d_argocd-server-deployment.yaml b/install/manifests/04d_argocd-server-deployment.yaml similarity index 100% rename from install/manifests/03d_argocd-server-deployment.yaml rename to install/manifests/04d_argocd-server-deployment.yaml diff --git a/install/manifests/03e_argocd-server-service.yaml b/install/manifests/04e_argocd-server-service.yaml similarity index 100% rename from install/manifests/03e_argocd-server-service.yaml rename to install/manifests/04e_argocd-server-service.yaml diff --git a/install/manifests/04a_argocd-repo-server-deployment.yaml b/install/manifests/05a_argocd-repo-server-deployment.yaml similarity index 100% rename from install/manifests/04a_argocd-repo-server-deployment.yaml rename to install/manifests/05a_argocd-repo-server-deployment.yaml diff --git a/install/manifests/04b_argocd-repo-server-service.yaml b/install/manifests/05b_argocd-repo-server-service.yaml similarity index 88% rename from install/manifests/04b_argocd-repo-server-service.yaml rename to install/manifests/05b_argocd-repo-server-service.yaml index 4c169ab3d252a..2278015166156 100644 --- a/install/manifests/04b_argocd-repo-server-service.yaml +++ b/install/manifests/05b_argocd-repo-server-service.yaml @@ -6,5 +6,6 @@ metadata: spec: ports: - port: 8081 + targetPort: 8081 selector: app: argocd-repo-server diff --git a/server/server.go b/server/server.go index fec8a94af5dcd..08b599e6beef0 100644 --- a/server/server.go +++ b/server/server.go @@ -16,7 +16,7 @@ import ( "github.com/argoproj/argo-cd/server/repository" "github.com/argoproj/argo-cd/server/session" "github.com/argoproj/argo-cd/server/version" - "github.com/argoproj/argo-cd/util" + "github.com/argoproj/argo-cd/util/config" grpc_util "github.com/argoproj/argo-cd/util/grpc" jsonutil "github.com/argoproj/argo-cd/util/json" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" @@ -43,14 +43,14 @@ type ArgoCDServer struct { kubeclientset kubernetes.Interface appclientset appclientset.Interface repoclientset reposerver.Clientset - settings util.ArgoCDSettings + settings config.ArgoCDSettings log *log.Entry } // NewServer returns a new instance of the ArgoCD API server func NewServer( - kubeclientset kubernetes.Interface, appclientset appclientset.Interface, repoclientset reposerver.Clientset, namespace, staticAssetsDir, configMapName string) *ArgoCDServer { - configManager := util.NewConfigManager(kubeclientset, namespace, configMapName) + kubeclientset kubernetes.Interface, appclientset appclientset.Interface, repoclientset reposerver.Clientset, namespace, staticAssetsDir string) *ArgoCDServer { + configManager := config.NewConfigManager(kubeclientset, namespace) settings, err := configManager.GetSettings() if err != nil { log.Fatal(err) @@ -62,7 +62,7 @@ func NewServer( repoclientset: repoclientset, log: log.NewEntry(log.New()), staticAssetsDir: staticAssetsDir, - settings: settings, + settings: *settings, } } diff --git a/server/session/session.go b/server/session/session.go index 4ba0760225799..37b55414da6e4 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -5,7 +5,9 @@ import ( "fmt" appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned" - "github.com/argoproj/argo-cd/util" + "github.com/argoproj/argo-cd/util/config" + "github.com/argoproj/argo-cd/util/password" + "github.com/argoproj/argo-cd/util/session" "k8s.io/client-go/kubernetes" ) @@ -14,11 +16,11 @@ type Server struct { ns string kubeclientset kubernetes.Interface appclientset appclientset.Interface - serversettings util.ArgoCDSettings + serversettings config.ArgoCDSettings } // NewServer returns a new instance of the Session service -func NewServer(namespace string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, serversettings util.ArgoCDSettings) *Server { +func NewServer(namespace string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, serversettings config.ArgoCDSettings) *Server { return &Server{ ns: namespace, appclientset: appclientset, @@ -49,13 +51,13 @@ func (s *Server) Create(ctx context.Context, q *SessionRequest) (*SessionRespons passwordHash = "" } - valid, _ := util.VerifyPassword(q.Password, passwordHash) + valid, _ := password.VerifyPassword(q.Password, passwordHash) if !valid { err := fmt.Errorf(invalidLoginError) return nil, err } - sessionManager := util.MakeSessionManager(s.serversettings.ServerSignature) + sessionManager := session.MakeSessionManager(s.serversettings.ServerSignature) token, err := sessionManager.Create(q.Username) if err != nil { token = "" diff --git a/util/config/configmanager.go b/util/config/configmanager.go new file mode 100644 index 0000000000000..b0dcb2875ed04 --- /dev/null +++ b/util/config/configmanager.go @@ -0,0 +1,123 @@ +package config + +import ( + "fmt" + + "github.com/argoproj/argo-cd/common" + apiv1 "k8s.io/api/core/v1" + apierr "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +// ArgoCDSettings holds in-memory runtime configuration options. +type ArgoCDSettings struct { + // LocalUsers holds users local to (stored on) the server. This is to be distinguished from any potential alternative future login providers (LDAP, SAML, etc.) that might ever be added. + LocalUsers map[string]string + + // ServerSignature holds the key used to generate JWT tokens. + ServerSignature []byte +} + +const ( + // configManagerAdminPasswordKey designates the key for a root password inside a Kubernetes secret. + configManagerAdminPasswordKey = "admin.password" + + // configManagerServerSignatureKey designates the key for a server secret key inside a Kubernetes secret. + configManagerServerSignatureKey = "server.secretkey" +) + +// ConfigManager holds config info for a new manager with which to access Kubernetes ConfigMaps. +type ConfigManager struct { + clientset kubernetes.Interface + namespace string +} + +// GetSettings retrieves settings from the ConfigManager. +func (mgr *ConfigManager) GetSettings() (*ArgoCDSettings, error) { + // TODO: we currently do not store anything in configmaps, yet. We eventually will (e.g. + // tuning parameters). Future settings/tunables should be stored here + _, err := mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + argoCDSecret, err := mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(common.ArgoCDSecretName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + var settings ArgoCDSettings + adminPasswordHash, ok := argoCDSecret.Data[configManagerAdminPasswordKey] + if !ok { + return nil, fmt.Errorf("admin user not found") + } + settings.LocalUsers = map[string]string{ + common.ArgoCDAdminUsername: string(adminPasswordHash), + } + secretKey, ok := argoCDSecret.Data[configManagerServerSignatureKey] + if !ok { + return nil, fmt.Errorf("server secret key not found") + } + settings.ServerSignature = secretKey + return &settings, nil +} + +// SaveSettings serializes ArgoCD settings and upserts it into K8s secret/configmap +func (mgr *ConfigManager) SaveSettings(settings *ArgoCDSettings) error { + configMapData := make(map[string]string) + _, err := mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName, metav1.GetOptions{}) + if err != nil { + if !apierr.IsNotFound(err) { + return err + } + newConfigMap := &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDConfigMapName, + }, + Data: configMapData, + } + _, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Create(newConfigMap) + if err != nil { + return err + } + } else { + // mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update() + } + + secretStringData := map[string]string{ + configManagerServerSignatureKey: string(settings.ServerSignature), + configManagerAdminPasswordKey: settings.LocalUsers[common.ArgoCDAdminUsername], + } + argoCDSecret, err := mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(common.ArgoCDSecretName, metav1.GetOptions{}) + if err != nil { + if !apierr.IsNotFound(err) { + return err + } + newSecret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ArgoCDSecretName, + }, + StringData: secretStringData, + } + _, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Create(newSecret) + if err != nil { + return err + } + } else { + argoCDSecret.Data = nil + argoCDSecret.StringData = secretStringData + _, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Update(argoCDSecret) + if err != nil { + return err + } + } + return nil +} + +// NewConfigManager generates a new ConfigManager pointer and returns it +func NewConfigManager(clientset kubernetes.Interface, namespace string) *ConfigManager { + return &ConfigManager{ + clientset: clientset, + namespace: namespace, + } +} diff --git a/util/configmanager.go b/util/configmanager.go deleted file mode 100644 index 775007c2491bf..0000000000000 --- a/util/configmanager.go +++ /dev/null @@ -1,211 +0,0 @@ -package util - -import ( - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/kubernetes" -) - -// ArgoCDSettings holds in-memory runtime configuration options. -type ArgoCDSettings struct { - // LocalUsers holds users local to (stored on) the server. This is to be distinguished from any potential alternative future login providers (LDAP, SAML, etc.) that might ever be added. - LocalUsers map[string]string - - // ServerSignature holds the key used to generate JWT tokens. - ServerSignature []byte -} - -type configMapData struct { - rootCredentialsSecretName string - serverSignatureSecretName string -} - -const ( - // defaultConfigMapName default name of config map with argo-cd settings - defaultConfigMapName = "argo-cd-cm" - - // defaultRootCredentialsSecretName contains default name of secret with root user credentials - defaultRootCredentialsSecretName = "argo-cd-root-credentials" - - // defaultServerSignatureSecretName contains the default name of a secret to hold the key used to generate JWT tokens. - defaultServerSignatureSecretName = "argo-cd-server-secret-key" - - // configManagerRootUsernameKey designates the key for a root username inside a Kubernetes secret. - configManagerRootUsernameKey = "root.username" - - // configManagerRootPasswordKey designates the key for a root password inside a Kubernetes secret. - configManagerRootPasswordKey = "root.password" - - // configManagerServerSignatureKey designates the key for a server secret key inside a Kubernetes secret. - configManagerServerSignatureKey = "server.secretkey" -) - -// ConfigManager holds config info for a new manager with which to access Kubernetes ConfigMaps. -type ConfigManager struct { - clientset kubernetes.Interface - namespace string - configMapName string -} - -// GetSettings retrieves settings from the ConfigManager. -func (mgr *ConfigManager) GetSettings() (ArgoCDSettings, error) { - settings := ArgoCDSettings{} - settings.LocalUsers = make(map[string]string) - data, err := mgr.getConfigMapData() - if err != nil { - return settings, err - } - - // Try to retrieve the root credentials from a K8s secret - rootCredentials, err := mgr.readSecret(data.rootCredentialsSecretName) - if err != nil && !errors.IsNotFound(err) { - return settings, err - } - // Retrieve credential info from the secret - rootUsername, okUsername := rootCredentials.Data[configManagerRootUsernameKey] - rootPassword, okPassword := rootCredentials.Data[configManagerRootPasswordKey] - - if okUsername && okPassword { - // Store credential info inside LocalUsers - settings.LocalUsers[string(rootUsername)] = string(rootPassword) - } - - // Try to retrieve the server secret key from a K8s secret - secretKey, err := mgr.readSecret(data.serverSignatureSecretName) - if err != nil && !errors.IsNotFound(err) { - return settings, err - } - secretKeyData := secretKey.Data[configManagerServerSignatureKey] - settings.ServerSignature = secretKeyData - - return settings, nil -} - -// GenerateServerSignature makes a new pseudorandom secret key for signing JWT tokens. -func (mgr *ConfigManager) GenerateServerSignature() error { - data, err := mgr.getConfigMapData() - if err != nil { - return err - } - - signature, err := makeSignature(32) - if err != nil { - return err - } - - signatureMap := map[string][]byte{ - configManagerServerSignatureKey: signature, - } - - secret, err := mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(data.serverSignatureSecretName, metav1.GetOptions{}) - if err != nil { - newSecret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: data.serverSignatureSecretName, - }, - Data: signatureMap, - } - _, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Create(newSecret) - - } else { - secret.Data = signatureMap - _, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Update(secret) - } - return err -} - -// SetRootUserCredentials sets the admin username and password for Web login. -func (mgr *ConfigManager) SetRootUserCredentials(username string, password string) error { - if username == password { - return fmt.Errorf("Username and password cannot be the same") - } - - data, err := mgr.getConfigMapData() - if err != nil { - return err - } - - // Don't commit plaintext passwords - passwordHash, err := HashPassword(password) - if err != nil { - return err - } - - credentials := map[string]string{ - configManagerRootUsernameKey: username, - configManagerRootPasswordKey: passwordHash, - } - - // See if we've already written this secret - secret, err := mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(data.rootCredentialsSecretName, metav1.GetOptions{}) - if err != nil { - newSecret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: data.rootCredentialsSecretName, - }, - } - newSecret.StringData = credentials - _, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Create(newSecret) - - } else { - secret.StringData = credentials - _, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Update(secret) - } - return err -} - -// NewConfigManager generates a new ConfigManager pointer and returns it -func NewConfigManager(clientset kubernetes.Interface, namespace, configMapName string) (mgr *ConfigManager) { - if configMapName == "" { - configMapName = defaultConfigMapName - } - mgr = &ConfigManager{ - clientset: clientset, - namespace: namespace, - configMapName: configMapName, - } - return -} - -func (mgr *ConfigManager) getConfigMapData() (configMapData, error) { - data := configMapData{} - configMap, err := mgr.readConfigMap(mgr.configMapName) - if err != nil { - if errors.IsNotFound(err) { - data.rootCredentialsSecretName = defaultRootCredentialsSecretName - data.serverSignatureSecretName = defaultServerSignatureSecretName - return data, nil - } else { - return data, err - } - } - rootCredentialsSecretName, ok := configMap.Data[defaultRootCredentialsSecretName] - if !ok { - rootCredentialsSecretName = defaultRootCredentialsSecretName - } - data.rootCredentialsSecretName = rootCredentialsSecretName - - serverSignatureSecretName, ok := configMap.Data[defaultServerSignatureSecretName] - if !ok { - serverSignatureSecretName = defaultServerSignatureSecretName - } - data.serverSignatureSecretName = serverSignatureSecretName - - return data, nil -} - -// ReadConfigMap retrieves a config map from Kubernetes. -func (mgr *ConfigManager) readConfigMap(name string) (configMap *apiv1.ConfigMap, err error) { - configMap, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Get(name, metav1.GetOptions{}) - return -} - -// ReadSecret retrieves a secret from Kubernetes. -func (mgr *ConfigManager) readSecret(name string) (secret *apiv1.Secret, err error) { - secret, err = mgr.clientset.CoreV1().Secrets(mgr.namespace).Get(name, metav1.GetOptions{}) - return -} diff --git a/util/password.go b/util/password/password.go similarity index 99% rename from util/password.go rename to util/password/password.go index d1c4013c151c2..dea3473cf3149 100644 --- a/util/password.go +++ b/util/password/password.go @@ -1,4 +1,4 @@ -package util +package password import ( "crypto/subtle" diff --git a/util/password_test.go b/util/password/password_test.go similarity index 99% rename from util/password_test.go rename to util/password/password_test.go index ecea84a988b23..2047f11a11cb8 100644 --- a/util/password_test.go +++ b/util/password/password_test.go @@ -1,4 +1,4 @@ -package util +package password import ( "testing" diff --git a/util/sessionmanager.go b/util/session/sessionmanager.go similarity index 97% rename from util/sessionmanager.go rename to util/session/sessionmanager.go index 1972318669cae..e6b0a645c7602 100644 --- a/util/sessionmanager.go +++ b/util/session/sessionmanager.go @@ -1,4 +1,4 @@ -package util +package session import ( "crypto/rand" @@ -74,7 +74,7 @@ func (mgr SessionManager) Parse(tokenString string) (*SessionManagerTokenClaims, } // MakeSignature generates a cryptographically-secure pseudo-random token, based on a given number of random bytes, for signing purposes. -func makeSignature(size int) ([]byte, error) { +func MakeSignature(size int) ([]byte, error) { b := make([]byte, size) _, err := rand.Read(b) if err != nil { diff --git a/util/sessionmanager_test.go b/util/session/sessionmanager_test.go similarity index 94% rename from util/sessionmanager_test.go rename to util/session/sessionmanager_test.go index d8af471214dad..c5cffa46d62d5 100644 --- a/util/sessionmanager_test.go +++ b/util/session/sessionmanager_test.go @@ -1,4 +1,4 @@ -package util +package session import ( "testing" @@ -29,7 +29,7 @@ func TestSessionManager(t *testing.T) { func TestMakeSignature(t *testing.T) { for size := 1; size <= 64; size++ { - s, err := makeSignature(size) + s, err := MakeSignature(size) if err != nil { t.Errorf("Could not generate signature of size %d: %v", size, err) }