diff --git a/cmd/controller/certificates.go b/cmd/controller/certificates.go index f994462ef832..9f8da99212b1 100644 --- a/cmd/controller/certificates.go +++ b/cmd/controller/certificates.go @@ -4,6 +4,7 @@ package controller import ( + "cmp" "context" "errors" "fmt" @@ -37,6 +38,12 @@ type Certificates struct { // Init initializes the certificate component func (c *Certificates) Init(ctx context.Context) error { + defaultUserNames := v1beta1.DefaultSystemUsers() + userNames := defaultUserNames + if c.ClusterSpec.Install != nil && c.ClusterSpec.Install.SystemUsers != nil { + userNames = c.ClusterSpec.Install.SystemUsers + } + eg, _ := errgroup.WithContext(ctx) // Common CA caCertPath := filepath.Join(c.K0sVars.CertRootDir, "ca.crt") @@ -56,9 +63,10 @@ func (c *Certificates) Init(ctx context.Context) error { // Changing the URL here also requires changes in the "k0s kubeconfig admin" subcommand. kubeConfigAPIUrl := c.ClusterSpec.API.LocalURL() - apiServerUID, err := users.LookupUID(constant.ApiserverUser) + apiServerUser := cmp.Or(userNames.KubeAPIServer, defaultUserNames.KubeAPIServer) + apiServerUID, err := users.LookupUID(apiServerUser) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.ApiserverUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", apiServerUser, err) apiServerUID = users.RootUID logrus.WithError(err).Warn("Files with key material for kube-apiserver user will be owned by root") } @@ -113,9 +121,10 @@ func (c *Certificates) Init(ctx context.Context) error { CAKey: caCertKey, } - uid, err := users.LookupUID(constant.KonnectivityServerUser) + konnectivityServerUser := cmp.Or(userNames.Konnectivity, defaultUserNames.Konnectivity) + uid, err := users.LookupUID(konnectivityServerUser) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.KonnectivityServerUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", konnectivityServerUser, err) uid = users.RootUID logrus.WithError(err).Warn("Files with key material for konnectivity-server user will be owned by root") } @@ -153,9 +162,10 @@ func (c *Certificates) Init(ctx context.Context) error { CAKey: caCertKey, } - uid, err := users.LookupUID(constant.SchedulerUser) + schedulerUser := cmp.Or(userNames.KubeScheduler, defaultUserNames.KubeScheduler) + uid, err := users.LookupUID(schedulerUser) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.SchedulerUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", schedulerUser, err) uid = users.RootUID logrus.WithError(err).Warn("Files with key material for kube-scheduler user will be owned by root") } diff --git a/cmd/controller/controller.go b/cmd/controller/controller.go index b0f72e15cdc7..8307efb0e14f 100644 --- a/cmd/controller/controller.go +++ b/cmd/controller/controller.go @@ -237,19 +237,35 @@ func (c *command) start(ctx context.Context, flags *config.ControllerOptions, de switch storageType { case v1beta1.KineStorageType: + userName := constant.KineUser + if i := nodeConfig.Spec.Install; i != nil && i.SystemUsers != nil && i.SystemUsers.Kine != "" { + userName = i.SystemUsers.Kine + } storageBackend = &controller.Kine{ - Config: nodeConfig.Spec.Storage.Kine, - K0sVars: c.K0sVars, + Config: nodeConfig.Spec.Storage.Kine, + K0sVars: c.K0sVars, + UserName: userName, } case v1beta1.EtcdStorageType: config := nodeConfig.Spec.Storage.Etcd if !config.IsExternalClusterUsed() { + userName, apiServerUserName := constant.EtcdUser, constant.ApiserverUser + if i := nodeConfig.Spec.Install; i != nil && i.SystemUsers != nil { + if i.SystemUsers.Etcd != "" { + userName = i.SystemUsers.Etcd + } + if i.SystemUsers.KubeAPIServer != "" { + apiServerUserName = i.SystemUsers.KubeAPIServer + } + } storageBackend = &controller.Etcd{ - CertManager: certificateManager, - Config: config, - JoinClient: joinClient, - K0sVars: c.K0sVars, - LogLevel: c.LogLevels.Etcd, + CertManager: certificateManager, + Config: config, + JoinClient: joinClient, + K0sVars: c.K0sVars, + LogLevel: c.LogLevels.Etcd, + UserName: userName, + APIServerUserName: apiServerUserName, } } default: @@ -301,10 +317,15 @@ func (c *command) start(ctx context.Context, flags *config.ControllerOptions, de enableKonnectivity := controllerMode != config.SingleNodeMode && !slices.Contains(flags.DisableComponents, constant.KonnectivityServerComponentName) if enableKonnectivity { + userName := constant.KonnectivityServerUser + if i := nodeConfig.Spec.Install; i != nil && i.SystemUsers != nil && i.SystemUsers.Konnectivity != "" { + userName = i.SystemUsers.Konnectivity + } nodeComponents.Add(ctx, &controller.Konnectivity{ Spec: nodeConfig.Spec.Konnectivity, K0sVars: c.K0sVars, LogLevel: c.LogLevels.Konnectivity, + UserName: userName, EventEmitter: prober.NewEventEmitter(), ServerCount: numActiveControllers.Peek, }) @@ -572,17 +593,29 @@ func (c *command) start(ctx context.Context, flags *config.ControllerOptions, de } if !slices.Contains(flags.DisableComponents, constant.KubeSchedulerComponentName) { + userName := constant.SchedulerUser + if i := nodeConfig.Spec.Install; i != nil && i.SystemUsers != nil && i.SystemUsers.KubeScheduler != "" { + userName = i.SystemUsers.KubeScheduler + } clusterComponents.Add(ctx, &controller.Scheduler{ LogLevel: c.LogLevels.KubeScheduler, K0sVars: c.K0sVars, + UserName: userName, DisableLeaderElection: singleController, }) } if !slices.Contains(flags.DisableComponents, constant.KubeControllerManagerComponentName) { + // controller manager running as api-server user as they both need access to same sa.key + userName := constant.ApiserverUser + if i := nodeConfig.Spec.Install; i != nil && i.SystemUsers != nil && i.SystemUsers.KubeAPIServer != "" { + userName = i.SystemUsers.KubeAPIServer + } + clusterComponents.Add(ctx, &controller.Manager{ LogLevel: c.LogLevels.KubeControllerManager, K0sVars: c.K0sVars, + UserName: userName, DisableLeaderElection: singleController, ServiceClusterIPRange: nodeConfig.Spec.Network.BuildServiceCIDR(nodeConfig.Spec.PrimaryAddressFamily()), ExtraArgs: flags.KubeControllerManagerExtraArgs, diff --git a/docs/configuration.md b/docs/configuration.md index 65a438e52629..e9b3cd40dead 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -123,6 +123,26 @@ spec: ## `spec` Key Detail +### `spec.installConfig` + +| Element | Description | +|---------|-------------------------------------------------| +| `users` | System users used to run controller components. | + +#### `spec.installConfig.users` + +Users will be created when running `k0s install`. They will own the generated +certificates and kubeconfigs and be used to execute the supervised processes. If +they don't exist, k0s will fallback to the root user. + +| Element | Description | +|---------------------|-------------------------------------------------------------------------------| +| `etcdUser` | Managed etcd user name. Default: `etcd`. | +| `kineUser` | Kine user. Default: `kube-apiserver`. | +| `konnectivityUser` | Konnectivity server user. Default: `konnectivity-server`. | +| `kubeAPIserverUser` | Kubernetes API server and controller manager user. Default: `kube-apiserver`. | +| `kubeSchedulerUser` | Kubernetes scheduler user. Default: `kube-scheduler`. | + ### `spec.api` | Element | Description | diff --git a/pkg/apis/k0s/v1beta1/clusterconfig_types.go b/pkg/apis/k0s/v1beta1/clusterconfig_types.go index 7281cf7a8b3e..20d3982d4a55 100644 --- a/pkg/apis/k0s/v1beta1/clusterconfig_types.go +++ b/pkg/apis/k0s/v1beta1/clusterconfig_types.go @@ -203,7 +203,7 @@ func canStrip(f reflect.StructField) bool { return false } -// InstallSpec defines the required fields for the `k0s install` command +// Installation-time settings and related runtime defaults. type InstallSpec struct { SystemUsers *SystemUser `json:"users,omitempty"` } diff --git a/pkg/apis/k0s/v1beta1/system.go b/pkg/apis/k0s/v1beta1/system.go index c52b1008ef4e..ab224b3c76a6 100644 --- a/pkg/apis/k0s/v1beta1/system.go +++ b/pkg/apis/k0s/v1beta1/system.go @@ -5,12 +5,20 @@ package v1beta1 import "github.com/k0sproject/k0s/pkg/constant" -// SystemUser defines the user to use for each component +// System users used to run controller components. Users will be created when +// running `k0s install`. They will own the generated certificates and +// kubeconfigs and be used to execute the supervised processes. If they don't +// exist, k0s will fallback to the root user. type SystemUser struct { - Etcd string `json:"etcdUser,omitempty"` - Kine string `json:"kineUser,omitempty"` - Konnectivity string `json:"konnectivityUser,omitempty"` + // User to use for managed etcd (default "etcd") + Etcd string `json:"etcdUser,omitempty"` + // User to use for kine (default "kube-apiserver") + Kine string `json:"kineUser,omitempty"` + // User to use for the konnectivity server (default "konnectivity-server") + Konnectivity string `json:"konnectivityUser,omitempty"` + // User to use for the Kubernetes API Server (default "kube-apiserver") KubeAPIServer string `json:"kubeAPIserverUser,omitempty"` + // User to use for the Kubernetes scheduler (default "kube-scheduler") KubeScheduler string `json:"kubeSchedulerUser,omitempty"` } diff --git a/pkg/component/controller/apiserver.go b/pkg/component/controller/apiserver.go index c2db1b521f61..f5f77802cf7e 100644 --- a/pkg/component/controller/apiserver.go +++ b/pkg/component/controller/apiserver.go @@ -76,11 +76,14 @@ type egressSelectorConfig struct { } // Init extracts needed binaries -func (a *APIServer) Init(_ context.Context) error { - var err error - a.uid, err = users.LookupUID(constant.ApiserverUser) +func (a *APIServer) Init(_ context.Context) (err error) { + userName := constant.ApiserverUser + if i := a.ClusterConfig.Spec.Install; i != nil && i.SystemUsers != nil && i.SystemUsers.KubeAPIServer != "" { + userName = i.SystemUsers.KubeAPIServer + } + a.uid, err = users.LookupUID(userName) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.ApiserverUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", userName, err) a.uid = users.RootUID logrus.WithError(err).Warn("Running Kubernetes API server as root") } diff --git a/pkg/component/controller/controllermanager.go b/pkg/component/controller/controllermanager.go index 6be47abc49cf..e69a0fcf14ad 100644 --- a/pkg/component/controller/controllermanager.go +++ b/pkg/component/controller/controllermanager.go @@ -19,7 +19,6 @@ import ( "github.com/k0sproject/k0s/pkg/assets" "github.com/k0sproject/k0s/pkg/component/manager" "github.com/k0sproject/k0s/pkg/config" - "github.com/k0sproject/k0s/pkg/constant" "github.com/k0sproject/k0s/pkg/supervisor" ) @@ -27,6 +26,7 @@ import ( type Manager struct { K0sVars *config.CfgVars LogLevel string + UserName string DisableLeaderElection bool ServiceClusterIPRange string ExtraArgs string @@ -54,10 +54,9 @@ var _ manager.Reconciler = (*Manager)(nil) // Init extracts the needed binaries func (a *Manager) Init(_ context.Context) error { var err error - // controller manager running as api-server user as they both need access to same sa.key - a.uid, err = users.LookupUID(constant.ApiserverUser) + a.uid, err = users.LookupUID(a.UserName) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.ApiserverUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", a.UserName, err) a.uid = users.RootUID logrus.WithError(err).Warn("Running Kubernetes controller manager as root") } diff --git a/pkg/component/controller/etcd.go b/pkg/component/controller/etcd.go index b6cadc72e178..e1b618831621 100644 --- a/pkg/component/controller/etcd.go +++ b/pkg/component/controller/etcd.go @@ -36,11 +36,13 @@ const etcdGID = 0 // Etcd implement the component interface to run etcd type Etcd struct { - CertManager certificate.Manager - Config *v1beta1.EtcdConfig - JoinClient *token.JoinClient - K0sVars *config.CfgVars - LogLevel string + CertManager certificate.Manager + Config *v1beta1.EtcdConfig + JoinClient *token.JoinClient + K0sVars *config.CfgVars + LogLevel string + UserName string + APIServerUserName string supervisor *supervisor.Supervisor executablePath string @@ -58,9 +60,9 @@ func (e *Etcd) Init(_ context.Context) error { return fmt.Errorf("missing environment variable: %w", err) } - e.uid, err = users.LookupUID(constant.EtcdUser) + e.uid, err = users.LookupUID(e.UserName) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.EtcdUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", e.UserName, err) e.uid = users.RootUID logrus.WithError(err).Warn("Running etcd as root, files with key material for etcd user will be owned by root") } @@ -272,11 +274,11 @@ func (e *Etcd) setupCerts(ctx context.Context) error { }, } - uid, err := users.LookupUID(constant.ApiserverUser) + uid, err := users.LookupUID(e.APIServerUserName) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.ApiserverUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", e.APIServerUserName, err) uid = users.RootUID - logrus.WithError(err).Warn("Files with key material for kube-apiserver user will be owned by root") + logrus.WithError(err).Warnf("Files with key material for %s user will be owned by root", e.APIServerUserName) } _, err = e.CertManager.EnsureCertificate(etcdCertReq, uid, e.Config.CA.CertificatesExpireAfter.Duration) diff --git a/pkg/component/controller/kine.go b/pkg/component/controller/kine.go index e4baea9cf7c5..b3aeddbcedde 100644 --- a/pkg/component/controller/kine.go +++ b/pkg/component/controller/kine.go @@ -33,8 +33,9 @@ const kineGID = 0 // Kine implement the component interface to run kine type Kine struct { - Config *v1beta1.KineConfig - K0sVars *config.CfgVars + Config *v1beta1.KineConfig + K0sVars *config.CfgVars + UserName string supervisor *supervisor.Supervisor executablePath string @@ -49,9 +50,9 @@ var _ manager.Ready = (*Kine)(nil) func (k *Kine) Init(_ context.Context) error { logrus.Infof("initializing kine") var err error - k.uid, err = users.LookupUID(constant.KineUser) + k.uid, err = users.LookupUID(k.UserName) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.KineUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", k.UserName, err) k.uid = users.RootUID logrus.WithError(err).Warn("Running kine as root") } diff --git a/pkg/component/controller/konnectivity.go b/pkg/component/controller/konnectivity.go index 5b1993ec515d..32dbdb50ea2a 100644 --- a/pkg/component/controller/konnectivity.go +++ b/pkg/component/controller/konnectivity.go @@ -34,6 +34,7 @@ type Konnectivity struct { Spec *v1beta1.KonnectivitySpec // FIXME: This should be reconciled via dynamic config K0sVars *config.CfgVars LogLevel string + UserName string ServerCount func() (uint, <-chan struct{}) supervisor *supervisor.Supervisor @@ -54,9 +55,9 @@ var _ prober.Healthz = (*Konnectivity)(nil) // Init ... func (k *Konnectivity) Init(ctx context.Context) error { var err error - k.uid, err = users.LookupUID(constant.KonnectivityServerUser) + k.uid, err = users.LookupUID(k.UserName) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.KonnectivityServerUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", k.UserName, err) k.uid = users.RootUID k.EmitWithPayload("error getting UID for", err) logrus.WithError(err).Warn("Running konnectivity as root") diff --git a/pkg/component/controller/scheduler.go b/pkg/component/controller/scheduler.go index 83340ff4dcf7..d7c75435073f 100644 --- a/pkg/component/controller/scheduler.go +++ b/pkg/component/controller/scheduler.go @@ -16,7 +16,6 @@ import ( "github.com/k0sproject/k0s/pkg/assets" "github.com/k0sproject/k0s/pkg/component/manager" "github.com/k0sproject/k0s/pkg/config" - "github.com/k0sproject/k0s/pkg/constant" "github.com/k0sproject/k0s/pkg/supervisor" ) @@ -24,6 +23,7 @@ import ( type Scheduler struct { K0sVars *config.CfgVars LogLevel string + UserName string DisableLeaderElection bool supervisor *supervisor.Supervisor @@ -40,9 +40,9 @@ const kubeSchedulerComponentName = "kube-scheduler" // Init extracts the needed binaries func (a *Scheduler) Init(_ context.Context) error { var err error - a.uid, err = users.LookupUID(constant.SchedulerUser) + a.uid, err = users.LookupUID(a.UserName) if err != nil { - err = fmt.Errorf("failed to lookup UID for %q: %w", constant.SchedulerUser, err) + err = fmt.Errorf("failed to lookup UID for %q: %w", a.UserName, err) a.uid = users.RootUID logrus.WithError(err).Warn("Running kube-scheduler as root") } diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index eb3f160deaa4..8f1138d1df54 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -49,17 +49,17 @@ const ( /* User accounts for services */ - // EtcdUser defines the user to use for running etcd process + // Default user to use for managed etcd EtcdUser = "etcd" - // KineUser defines the user to use for running kine process - KineUser = "kube-apiserver" // apiserver needs to be able to read the kine unix socket - // ApiserverUser defines the user to use for running k8s api-server process + // Default user to use for kine + KineUser = "kube-apiserver" // api-server needs to be able to read the kine unix socket + // Default user to use for the Kubernetes API Server ApiserverUser = "kube-apiserver" - // SchedulerUser defines the user to use for running k8s scheduler + // Default user to use for the Kubernetes scheduler SchedulerUser = "kube-scheduler" - // KonnectivityServerUser defines the user to use for konnectivity-server + // Default user to use for the konnectivity server KonnectivityServerUser = "konnectivity-server" - // KeepalivedUser defines the user to use for running keepalived + // User to use for keepalived KeepalivedUser = "keepalived" // KubernetesMajorMinorVersion defines the current embedded major.minor version info diff --git a/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml b/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml index dbf0cb8a5a76..32328203d13c 100644 --- a/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml +++ b/static/_crds/k0s/k0s.k0sproject.io_clusterconfigs.yaml @@ -481,21 +481,32 @@ spec: type: object type: object installConfig: - description: InstallSpec defines the required fields for the `k0s - install` command + description: Installation-time settings and related runtime defaults. properties: users: - description: SystemUser defines the user to use for each component + description: |- + System users used to run controller components. Users will be created when + running `k0s install`. They will own the generated certificates and + kubeconfigs and be used to execute the supervised processes. If they don't + exist, k0s will fallback to the root user. properties: etcdUser: + description: User to use for managed etcd (default "etcd") type: string kineUser: + description: User to use for kine (default "kube-apiserver") type: string konnectivityUser: + description: User to use for the konnectivity server (default + "konnectivity-server") type: string kubeAPIserverUser: + description: User to use for the Kubernetes API Server (default + "kube-apiserver") type: string kubeSchedulerUser: + description: User to use for the Kubernetes scheduler (default + "kube-scheduler") type: string type: object type: object