diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 6cfa4354f7..7fce0f6ec4 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -44,6 +44,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/utils/pointer" "agones.dev/agones/pkg" "agones.dev/agones/pkg/client/clientset/versioned" @@ -59,33 +60,36 @@ import ( ) const ( - enableStackdriverMetricsFlag = "stackdriver-exporter" - stackdriverLabels = "stackdriver-labels" - enablePrometheusMetricsFlag = "prometheus-exporter" - projectIDFlag = "gcp-project-id" - sidecarImageFlag = "sidecar-image" - sidecarCPURequestFlag = "sidecar-cpu-request" - sidecarCPULimitFlag = "sidecar-cpu-limit" - sidecarMemoryRequestFlag = "sidecar-memory-request" - sidecarMemoryLimitFlag = "sidecar-memory-limit" - sdkServerAccountFlag = "sdk-service-account" - pullSidecarFlag = "always-pull-sidecar" - minPortFlag = "min-port" - maxPortFlag = "max-port" - additionalPortRangesFlag = "additional-port-ranges" - certFileFlag = "cert-file" - keyFileFlag = "key-file" - numWorkersFlag = "num-workers" - apiServerSustainedQPSFlag = "api-server-qps" - apiServerBurstQPSFlag = "api-server-qps-burst" - logDirFlag = "log-dir" - logLevelFlag = "log-level" - logSizeLimitMBFlag = "log-size-limit-mb" - kubeconfigFlag = "kubeconfig" - allocationBatchWaitTime = "allocation-batch-wait-time" - defaultResync = 30 * time.Second - podNamespace = "pod-namespace" - leaderElectionFlag = "leader-election" + enableStackdriverMetricsFlag = "stackdriver-exporter" + stackdriverLabels = "stackdriver-labels" + enablePrometheusMetricsFlag = "prometheus-exporter" + projectIDFlag = "gcp-project-id" + sidecarImageFlag = "sidecar-image" + sidecarCPURequestFlag = "sidecar-cpu-request" + sidecarCPULimitFlag = "sidecar-cpu-limit" + sidecarMemoryRequestFlag = "sidecar-memory-request" + sidecarMemoryLimitFlag = "sidecar-memory-limit" + sidecarRunAsNonRootFlag = "sidecar-run-as-nonroot" + sidecarRunAsUserFlag = "sidercar-run-as-user" + sidercarAllowPrivilegeEscalationFlag = "sidercar-allow-privilege-escalation" + sdkServerAccountFlag = "sdk-service-account" + pullSidecarFlag = "always-pull-sidecar" + minPortFlag = "min-port" + maxPortFlag = "max-port" + additionalPortRangesFlag = "additional-port-ranges" + certFileFlag = "cert-file" + keyFileFlag = "key-file" + numWorkersFlag = "num-workers" + apiServerSustainedQPSFlag = "api-server-qps" + apiServerBurstQPSFlag = "api-server-qps-burst" + logDirFlag = "log-dir" + logLevelFlag = "log-level" + logSizeLimitMBFlag = "log-size-limit-mb" + kubeconfigFlag = "kubeconfig" + allocationBatchWaitTime = "allocation-batch-wait-time" + defaultResync = 30 * time.Second + podNamespace = "pod-namespace" + leaderElectionFlag = "leader-election" ) var ( @@ -211,10 +215,15 @@ func main() { gsCounter := gameservers.NewPerNodeCounter(kubeInformerFactory, agonesInformerFactory) + securityCtx := corev1.SecurityContext{ + RunAsNonRoot: pointer.Bool(ctlConf.SidecarRunAsNonroot), + RunAsUser: pointer.Int64(int64(ctlConf.SidecarRunAsUser)), + AllowPrivilegeEscalation: pointer.Bool(ctlConf.SidecarAllowPrivilegeEscalation), + } gsController := gameservers.NewController(controllerHooks, health, ctlConf.PortRanges, ctlConf.SidecarImage, ctlConf.AlwaysPullSidecar, ctlConf.SidecarCPURequest, ctlConf.SidecarCPULimit, - ctlConf.SidecarMemoryRequest, ctlConf.SidecarMemoryLimit, ctlConf.SdkServiceAccount, + ctlConf.SidecarMemoryRequest, ctlConf.SidecarMemoryLimit, securityCtx, ctlConf.SdkServiceAccount, kubeClient, kubeInformerFactory, extClient, agonesClient, agonesInformerFactory) gsSetController := gameserversets.NewController(health, gsCounter, kubeClient, extClient, agonesClient, agonesInformerFactory) @@ -260,6 +269,9 @@ func parseEnvFlags() config { viper.SetDefault(sidecarCPULimitFlag, "0") viper.SetDefault(sidecarMemoryRequestFlag, "0") viper.SetDefault(sidecarMemoryLimitFlag, "0") + viper.SetDefault(sidecarRunAsNonRootFlag, "true") + viper.SetDefault(sidecarRunAsUserFlag, "1000") + viper.SetDefault(sidercarAllowPrivilegeEscalationFlag, "false") viper.SetDefault(pullSidecarFlag, false) viper.SetDefault(sdkServerAccountFlag, "agones-sdk") viper.SetDefault(certFileFlag, filepath.Join(base, "certs", "server.crt")) @@ -284,6 +296,9 @@ func parseEnvFlags() config { pflag.String(sidecarCPURequestFlag, viper.GetString(sidecarCPURequestFlag), "Flag to overwrite the GameServer sidecar container's cpu request. Can also use SIDECAR_CPU_REQUEST env variable") pflag.String(sidecarMemoryLimitFlag, viper.GetString(sidecarMemoryLimitFlag), "Flag to overwrite the GameServer sidecar container's memory limit. Can also use SIDECAR_MEMORY_LIMIT env variable") pflag.String(sidecarMemoryRequestFlag, viper.GetString(sidecarMemoryRequestFlag), "Flag to overwrite the GameServer sidecar container's memory request. Can also use SIDECAR_MEMORY_REQUEST env variable") + pflag.Bool(sidecarRunAsNonRootFlag, viper.GetBool(sidecarRunAsNonRootFlag), "Flag to indicate the GameServer sidecar container must run as non-root user. Can also use SIDECAR_RUN_AS_NONROOT env variable") + pflag.Int32(sidecarRunAsUserFlag, 1000, "Flag to indicate the GameServer sidecar container's UID. Can also use SIDECAR_RUN_AS_USER env variable") + pflag.Bool(sidercarAllowPrivilegeEscalationFlag, viper.GetBool(sidercarAllowPrivilegeEscalationFlag), "Flag to indicate whether the GameServer sidecar container can gain more privileges than its parent process. Can also use SIDECAR_ALLOW_PRIVILEGE_ESCALATION env variable") pflag.Bool(pullSidecarFlag, viper.GetBool(pullSidecarFlag), "For development purposes, set the sidecar image to have a ImagePullPolicy of Always. Can also use ALWAYS_PULL_SIDECAR env variable") pflag.String(sdkServerAccountFlag, viper.GetString(sdkServerAccountFlag), "Overwrite what service account default for GameServer Pods. Defaults to Can also use SDK_SERVICE_ACCOUNT") pflag.Int32(minPortFlag, 0, "Required. The minimum port that that a GameServer can be allocated to. Can also use MIN_PORT env variable.") @@ -315,6 +330,9 @@ func parseEnvFlags() config { runtime.Must(viper.BindEnv(sidecarCPURequestFlag)) runtime.Must(viper.BindEnv(sidecarMemoryLimitFlag)) runtime.Must(viper.BindEnv(sidecarMemoryRequestFlag)) + runtime.Must(viper.BindEnv(sidecarRunAsNonRootFlag)) + runtime.Must(viper.BindEnv(sidecarRunAsUserFlag)) + runtime.Must(viper.BindEnv(sidercarAllowPrivilegeEscalationFlag)) runtime.Must(viper.BindEnv(pullSidecarFlag)) runtime.Must(viper.BindEnv(sdkServerAccountFlag)) runtime.Must(viper.BindEnv(minPortFlag)) @@ -372,30 +390,33 @@ func parseEnvFlags() config { } return config{ - PortRanges: portRanges, - SidecarImage: viper.GetString(sidecarImageFlag), - SidecarCPURequest: requestCPU, - SidecarCPULimit: limitCPU, - SidecarMemoryRequest: requestMemory, - SidecarMemoryLimit: limitMemory, - SdkServiceAccount: viper.GetString(sdkServerAccountFlag), - AlwaysPullSidecar: viper.GetBool(pullSidecarFlag), - KeyFile: viper.GetString(keyFileFlag), - CertFile: viper.GetString(certFileFlag), - KubeConfig: viper.GetString(kubeconfigFlag), - PrometheusMetrics: viper.GetBool(enablePrometheusMetricsFlag), - Stackdriver: viper.GetBool(enableStackdriverMetricsFlag), - GCPProjectID: viper.GetString(projectIDFlag), - NumWorkers: int(viper.GetInt32(numWorkersFlag)), - APIServerSustainedQPS: int(viper.GetInt32(apiServerSustainedQPSFlag)), - APIServerBurstQPS: int(viper.GetInt32(apiServerBurstQPSFlag)), - LogDir: viper.GetString(logDirFlag), - LogLevel: viper.GetString(logLevelFlag), - LogSizeLimitMB: int(viper.GetInt32(logSizeLimitMBFlag)), - StackdriverLabels: viper.GetString(stackdriverLabels), - AllocationBatchWaitTime: viper.GetDuration(allocationBatchWaitTime), - PodNamespace: viper.GetString(podNamespace), - LeaderElection: viper.GetBool(leaderElectionFlag), + PortRanges: portRanges, + SidecarImage: viper.GetString(sidecarImageFlag), + SidecarCPURequest: requestCPU, + SidecarCPULimit: limitCPU, + SidecarMemoryRequest: requestMemory, + SidecarMemoryLimit: limitMemory, + SidecarRunAsNonroot: viper.GetBool(sidecarRunAsNonRootFlag), + SidecarRunAsUser: int(viper.GetInt32(sidecarRunAsUserFlag)), + SidecarAllowPrivilegeEscalation: viper.GetBool(sidercarAllowPrivilegeEscalationFlag), + SdkServiceAccount: viper.GetString(sdkServerAccountFlag), + AlwaysPullSidecar: viper.GetBool(pullSidecarFlag), + KeyFile: viper.GetString(keyFileFlag), + CertFile: viper.GetString(certFileFlag), + KubeConfig: viper.GetString(kubeconfigFlag), + PrometheusMetrics: viper.GetBool(enablePrometheusMetricsFlag), + Stackdriver: viper.GetBool(enableStackdriverMetricsFlag), + GCPProjectID: viper.GetString(projectIDFlag), + NumWorkers: int(viper.GetInt32(numWorkersFlag)), + APIServerSustainedQPS: int(viper.GetInt32(apiServerSustainedQPSFlag)), + APIServerBurstQPS: int(viper.GetInt32(apiServerBurstQPSFlag)), + LogDir: viper.GetString(logDirFlag), + LogLevel: viper.GetString(logLevelFlag), + LogSizeLimitMB: int(viper.GetInt32(logSizeLimitMBFlag)), + StackdriverLabels: viper.GetString(stackdriverLabels), + AllocationBatchWaitTime: viper.GetDuration(allocationBatchWaitTime), + PodNamespace: viper.GetString(podNamespace), + LeaderElection: viper.GetBool(leaderElectionFlag), } } @@ -424,30 +445,33 @@ func parsePortRanges(s string) (map[string]portallocator.PortRange, error) { // config stores all required configuration to create a game server controller. type config struct { - PortRanges map[string]portallocator.PortRange - SidecarImage string - SidecarCPURequest resource.Quantity - SidecarCPULimit resource.Quantity - SidecarMemoryRequest resource.Quantity - SidecarMemoryLimit resource.Quantity - SdkServiceAccount string - AlwaysPullSidecar bool - PrometheusMetrics bool - Stackdriver bool - StackdriverLabels string - KeyFile string - CertFile string - KubeConfig string - GCPProjectID string - NumWorkers int - APIServerSustainedQPS int - APIServerBurstQPS int - LogDir string - LogLevel string - LogSizeLimitMB int - AllocationBatchWaitTime time.Duration - PodNamespace string - LeaderElection bool + PortRanges map[string]portallocator.PortRange + SidecarImage string + SidecarCPURequest resource.Quantity + SidecarCPULimit resource.Quantity + SidecarMemoryRequest resource.Quantity + SidecarMemoryLimit resource.Quantity + SidecarRunAsNonroot bool + SidecarRunAsUser int + SidecarAllowPrivilegeEscalation bool + SdkServiceAccount string + AlwaysPullSidecar bool + PrometheusMetrics bool + Stackdriver bool + StackdriverLabels string + KeyFile string + CertFile string + KubeConfig string + GCPProjectID string + NumWorkers int + APIServerSustainedQPS int + APIServerBurstQPS int + LogDir string + LogLevel string + LogSizeLimitMB int + AllocationBatchWaitTime time.Duration + PodNamespace string + LeaderElection bool } // validate ensures the ctlConfig data is valid. diff --git a/install/helm/agones/templates/controller.yaml b/install/helm/agones/templates/controller.yaml index a5a7045537..0d444ecaed 100644 --- a/install/helm/agones/templates/controller.yaml +++ b/install/helm/agones/templates/controller.yaml @@ -134,6 +134,12 @@ spec: value: {{ .Values.agones.image.sdk.memoryRequest | quote }} - name: SIDECAR_MEMORY_LIMIT value: {{ .Values.agones.image.sdk.memoryLimit | quote }} + - name: SIDECAR_RUN_AS_NONROOT + value: "true" + - name: SIDECAR_RUN_AS_USER + value: "1000" + - name: SIDECAR_ALLOW_PRIVILEGE_ESCALATION + value: "false" - name: SDK_SERVICE_ACCOUNT value: {{ .Values.agones.serviceaccount.sdk.name | quote }} - name: PROMETHEUS_EXPORTER diff --git a/install/yaml/install.yaml b/install/yaml/install.yaml index b782919a3f..f0eb12ada4 100644 --- a/install/yaml/install.yaml +++ b/install/yaml/install.yaml @@ -17084,6 +17084,12 @@ spec: value: "0" - name: SIDECAR_MEMORY_LIMIT value: "0" + - name: SIDECAR_RUN_AS_NONROOT + value: "true" + - name: SIDECAR_RUN_AS_USER + value: "1000" + - name: SIDECAR_ALLOW_PRIVILEGE_ESCALATION + value: "false" - name: SDK_SERVICE_ACCOUNT value: "agones-sdk" - name: PROMETHEUS_EXPORTER diff --git a/pkg/gameservers/controller.go b/pkg/gameservers/controller.go index 12b3d01aec..abeb005db1 100644 --- a/pkg/gameservers/controller.go +++ b/pkg/gameservers/controller.go @@ -56,7 +56,6 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" - "k8s.io/utils/pointer" ) const ( @@ -84,6 +83,7 @@ type Controller struct { sidecarCPULimit resource.Quantity sidecarMemoryRequest resource.Quantity sidecarMemoryLimit resource.Quantity + sidecarSecurityCtx corev1.SecurityContext sdkServiceAccount string crdGetter apiextclientv1.CustomResourceDefinitionInterface podGetter typedcorev1.PodsGetter @@ -115,6 +115,7 @@ func NewController( sidecarCPULimit resource.Quantity, sidecarMemoryRequest resource.Quantity, sidecarMemoryLimit resource.Quantity, + sidecarSecurityCtx corev1.SecurityContext, sdkServiceAccount string, kubeClient kubernetes.Interface, kubeInformerFactory informers.SharedInformerFactory, @@ -134,6 +135,7 @@ func NewController( sidecarCPURequest: sidecarCPURequest, sidecarMemoryLimit: sidecarMemoryLimit, sidecarMemoryRequest: sidecarMemoryRequest, + sidecarSecurityCtx: sidecarSecurityCtx, alwaysPullSidecarImage: alwaysPullSidecarImage, sdkServiceAccount: sdkServiceAccount, crdGetter: extClient.ApiextensionsV1().CustomResourceDefinitions(), @@ -765,11 +767,7 @@ func (c *Controller) sidecar(gs *agonesv1.GameServer) corev1.Container { sidecar.ImagePullPolicy = corev1.PullAlways } - sidecar.SecurityContext = &corev1.SecurityContext{ - AllowPrivilegeEscalation: pointer.Bool(false), - RunAsNonRoot: pointer.Bool(true), - RunAsUser: pointer.Int64(1000), - } + sidecar.SecurityContext = &c.sidecarSecurityCtx return sidecar } diff --git a/pkg/gameservers/controller_test.go b/pkg/gameservers/controller_test.go index 1813ea0fc9..d31cf4d4dc 100644 --- a/pkg/gameservers/controller_test.go +++ b/pkg/gameservers/controller_test.go @@ -48,6 +48,7 @@ import ( "k8s.io/apimachinery/pkg/watch" k8stesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" + "k8s.io/utils/pointer" ) const ( @@ -2254,13 +2255,18 @@ func testWithNonZeroDeletionTimestamp(t *testing.T, f func(*Controller, *agonesv // newFakeController returns a controller, backed by the fake Clientset func newFakeController() (*Controller, agtesting.Mocks) { m := agtesting.NewMocks() + securityCtx := corev1.SecurityContext{ + RunAsNonRoot: pointer.Bool(true), + RunAsUser: pointer.Int64(1000), + AllowPrivilegeEscalation: pointer.Bool((false)), + } c := NewController( generic.New(), healthcheck.NewHandler(), map[string]portallocator.PortRange{agonesv1.DefaultPortRange: {MinPort: 10, MaxPort: 20}}, "sidecar:dev", false, resource.MustParse("0.05"), resource.MustParse("0.1"), - resource.MustParse("50Mi"), resource.MustParse("100Mi"), "sdk-service-account", + resource.MustParse("50Mi"), resource.MustParse("100Mi"), securityCtx, "sdk-service-account", m.KubeClient, m.KubeInformerFactory, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory) c.recorder = m.FakeRecorder return c, m