From 2d906de4a54b0b5c722e50b22810e5f30219f278 Mon Sep 17 00:00:00 2001 From: Tom Wieczorek Date: Fri, 8 May 2026 13:26:30 +0200 Subject: [PATCH] Use configured install users for controller components Although the install command creates custom system users during installation, the controller components still use hard-coded default user names. Use the users specified in the config's install section for the generated certificates and kubeconfigs, as well as for executing the supervised processes. The config naming is unfortunate because it is now used during both installation and execution. However, if the users are not used for execution creating them during installation is futile. Signed-off-by: Tom Wieczorek --- cmd/controller/certificates.go | 22 ++++++--- cmd/controller/controller.go | 47 ++++++++++++++++--- docs/configuration.md | 20 ++++++++ pkg/apis/k0s/v1beta1/clusterconfig_types.go | 2 +- pkg/apis/k0s/v1beta1/system.go | 16 +++++-- pkg/component/controller/apiserver.go | 11 +++-- pkg/component/controller/controllermanager.go | 7 ++- pkg/component/controller/etcd.go | 22 +++++---- pkg/component/controller/kine.go | 9 ++-- pkg/component/controller/konnectivity.go | 5 +- pkg/component/controller/scheduler.go | 6 +-- pkg/constant/constant.go | 14 +++--- .../k0s/k0s.k0sproject.io_clusterconfigs.yaml | 17 +++++-- 13 files changed, 143 insertions(+), 55 deletions(-) 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