From f6833e8d1c6c7eb1bc8884599edb8a2061f6a5b9 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Mon, 4 Dec 2023 21:17:19 +0000 Subject: [PATCH] fix(impersonation): Fallback to using in-cluster config when Impersonate* headers in request --- docs/common/arguments.md | 50 ++++++++++----------- modules/api/main.go | 10 ++--- modules/api/pkg/args/builder.go | 10 ++--- modules/api/pkg/args/holder.go | 8 ++-- modules/api/pkg/client/manager.go | 25 +++++++---- modules/api/pkg/integration/manager_test.go | 4 +- 6 files changed, 57 insertions(+), 50 deletions(-) diff --git a/docs/common/arguments.md b/docs/common/arguments.md index 1d1992214a6a..d5c8815c5dfa 100644 --- a/docs/common/arguments.md +++ b/docs/common/arguments.md @@ -4,31 +4,31 @@ Dashboard containers accept multiple arguments that can be used to customize the ## Dashboard API arguments -| Argument name | Default value | Description | -|-----------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| insecure-port | 9000 | The port to listen to for incoming HTTP requests. | -| port | 9001 | The secure port to listen to for incoming HTTPS requests. | -| insecure-bind-address | 127.0.0.1 | The IP address on which to serve the `--insecure-port` (set to 127.0.0.1 for loopback only). | -| bind-address | 0.0.0.0 | The IP address on which to serve the `--port` (set to 0.0.0.0 for all interfaces). | -| default-cert-dir | /certs | Directory path containing `--tls-cert-file` and `--tls-key-file` files. Used also when auto-generating certificates flag is set. Relative to the container, not the host. | -| tls-cert-file | - | File containing the default x509 Certificate for HTTPS. | -| tls-key-file | - | File containing the default x509 private key matching --tls-cert-file. | -| auto-generate-certificates | false | When set to true, Dashboard will automatically generate certificates used to serve HTTPS. | -| apiserver-host | - | The address of the Kubernetes Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8080. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and local discovery is attempted. | -| namespace-header | - | The name of the request header that contains authorized namespaces. Used in multi-tenant clusters with [namespace-per-tenant isolation](https://kubernetes.io/docs/concepts/security/multi-tenancy/#namespace-per-tenant) that lack cluster-scoped RBAC to list namespaces in the cluster. | -| namespace-pattern | - | A regular expression applied to the header data contained by `--namespace-header`. Used to extract the authorized namespace(s) from the header data. If not specified, the `--namespace-header` data is expected to contain a list of namespace names. | -| api-log-level | INFO | Level of API request logging. Should be one of `INFO\|NONE\|DEBUG`. | -| heapster-host | - | The address of the Heapster Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8082. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and service proxy will be used. | -| sidecar-host | - | The address of the Sidecar Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8000. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and service proxy will be used. | -| metrics-provider | sidecar | Select provider type for metrics. 'none' will not check metrics. | -| metric-client-check-period | 30 | Time in seconds that defines how often configured metric client health check should be run. | -| kubeconfig | - | Path to kubeconfig file with authorization and control plane location information. | -| namespace | kube-system | When non-default namespace is used, create encryption key in the specified namespace. | -| token-ttl | 900 | Expiration time (in seconds) of JWE tokens generated by dashboard. '0' never expires. | -| authentication-mode | token | Enables authentication options that will be reflected on the login screen in the same order as provided. Multiple options can be used at once. Supported values: token, basic. Note that basic option should only be used if apiserver has '--authorization-mode=ABAC' and '--basic-auth-file' flags set. | -| enable-insecure-login | false | When enabled, Dashboard login view will also be shown when Dashboard is not served over HTTPS. | -| enable-skip-login | false | When enabled, the skip button on the login page will be shown. | -| disable-settings-authorizer | false | When enabled, Dashboard settings page will not require user to be logged in and authorized to access settings page. | +| Argument name | Default value | Description | +|--------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| insecure-port | 9000 | The port to listen to for incoming HTTP requests. | +| port | 9001 | The secure port to listen to for incoming HTTPS requests. | +| insecure-bind-address | 127.0.0.1 | The IP address on which to serve the `--insecure-port` (set to 127.0.0.1 for loopback only). | +| bind-address | 0.0.0.0 | The IP address on which to serve the `--port` (set to 0.0.0.0 for all interfaces). | +| default-cert-dir | /certs | Directory path containing `--tls-cert-file` and `--tls-key-file` files. Used also when auto-generating certificates flag is set. Relative to the container, not the host. | +| tls-cert-file | - | File containing the default x509 Certificate for HTTPS. | +| tls-key-file | - | File containing the default x509 private key matching --tls-cert-file. | +| auto-generate-certificates | false | When set to true, Dashboard will automatically generate certificates used to serve HTTPS. | +| apiserver-host | - | The address of the Kubernetes Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8080. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and local discovery is attempted. | +| namespace-header | - | The name of the request header that contains authorized namespaces. Used in multi-tenant clusters with [namespace-per-tenant isolation](https://kubernetes.io/docs/concepts/security/multi-tenancy/#namespace-per-tenant) that lack cluster-scoped RBAC to list namespaces in the cluster. | +| namespace-pattern | - | A regular expression applied to the header data contained by `--namespace-header`. Used to extract the authorized namespace(s) from the header data. If not specified, the `--namespace-header` data is expected to contain a list of namespace names. | +| api-log-level | INFO | Level of API request logging. Should be one of `INFO\|NONE\|DEBUG`. | +| heapster-host | - | The address of the Heapster Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8082. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and service proxy will be used. | +| sidecar-host | - | The address of the Sidecar Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8000. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and service proxy will be used. | +| metrics-provider | sidecar | Select provider type for metrics. 'none' will not check metrics. | +| metric-client-check-period | 30 | Time in seconds that defines how often configured metric client health check should be run. | +| kubeconfig | - | Path to kubeconfig file with authorization and control plane location information. | +| namespace | kube-system | When non-default namespace is used, create encryption key in the specified namespace. | +| token-ttl | 900 | Expiration time (in seconds) of JWE tokens generated by dashboard. '0' never expires. | +| authentication-mode | token | Enables authentication options that will be reflected on the login screen in the same order as provided. Multiple options can be used at once. Supported values: token, basic. Note that basic option should only be used if apiserver has '--authorization-mode=ABAC' and '--basic-auth-file' flags set. | +| enable-insecure-login | false | When enabled, Dashboard login view will also be shown when Dashboard is not served over HTTPS. | +| enable-skip-login | false | When enabled, the skip button on the login page will be shown. | +| disable-settings-authorizer | false | When enabled, Dashboard settings page will not require user to be logged in and authorized to access settings page. | # Dashboard Web arguments diff --git a/modules/api/main.go b/modules/api/main.go index 4d40e49237dd..15e3dacf99dc 100644 --- a/modules/api/main.go +++ b/modules/api/main.go @@ -57,7 +57,7 @@ var ( argMetricsProvider = pflag.String("metrics-provider", "sidecar", "select provider type for metrics, 'none' will not check metrics") argHeapsterHost = pflag.String("heapster-host", "", "address of the Heapster API server to connect to in the format of protocol://address:port, leave it empty if the binary runs inside cluster for service proxy usage") argSidecarHost = pflag.String("sidecar-host", "", "address of the Sidecar API server to connect to in the format of protocol://address:port, leave it empty if the binary runs inside cluster for service proxy usage") - argKubeConfigFile = pflag.String("kubeconfig", "", "path to kubeconfig file with authorization and control plane location information") + argKubeconfigFile = pflag.String("kubeconfig", "", "path to kubeconfig file with authorization and control plane location information") argTokenTTL = pflag.Int("token-ttl", authApi.DefaultTokenTTL, "expiration time in seconds of JWE tokens generated by dashboard, set to 0 to avoid expiration") argAuthenticationMode = pflag.StringSlice("authentication-mode", []string{authApi.Token.String()}, "enabled authentication options, supports 'token' and 'basic' that should only be used if Kubernetes API server has --authorization-mode=ABAC and --basic-auth-file flags set") argMetricClientCheckPeriod = pflag.Int("metric-client-check-period", 30, "time interval between separate metric client health checks in seconds") @@ -83,8 +83,8 @@ func main() { if args.Holder.GetApiserverHost() != "" { log.Printf("Using apiserver-host location: %s", args.Holder.GetApiserverHost()) } - if args.Holder.GetKubeConfigFile() != "" { - log.Printf("Using kubeconfig file: %s", args.Holder.GetKubeConfigFile()) + if args.Holder.GetKubeconfigFile() != "" { + log.Printf("Using kubeconfig file: %s", args.Holder.GetKubeconfigFile()) } if args.Holder.GetNamespace() != "" { log.Printf("Using namespace: %s", args.Holder.GetNamespace()) @@ -101,7 +101,7 @@ func main() { log.Printf("Applying regex pattern to namespaces header: %s", args.Holder.GetNamespacePattern()) } - clientManager := client.NewClientManager(args.Holder.GetKubeConfigFile(), args.Holder.GetApiserverHost()) + clientManager := client.NewClientManager(args.Holder.GetKubeconfigFile(), args.Holder.GetApiserverHost()) versionInfo, err := clientManager.InsecureClient().Discovery().ServerVersion() if err != nil { handleFatalInitError(err) @@ -228,7 +228,7 @@ func initArgHolder() { builder.SetMetricsProvider(*argMetricsProvider) builder.SetHeapsterHost(*argHeapsterHost) builder.SetSidecarHost(*argSidecarHost) - builder.SetKubeConfigFile(*argKubeConfigFile) + builder.SetKubeconfigFile(*argKubeconfigFile) builder.SetAPILogLevel(*argAPILogLevel) builder.SetAuthenticationMode(*argAuthenticationMode) builder.SetAutoGenerateCertificates(*argAutoGenerateCertificates) diff --git a/modules/api/pkg/args/builder.go b/modules/api/pkg/args/builder.go index 1a6a5bc80d38..e899b045f4d1 100644 --- a/modules/api/pkg/args/builder.go +++ b/modules/api/pkg/args/builder.go @@ -79,8 +79,8 @@ func (self *holderBuilder) SetKeyFile(keyFile string) *holderBuilder { } // SetApiserverHost 'apiserver-host' argument of Dashboard binary. -func (self *holderBuilder) SetApiserverHost(apiServerHost string) *holderBuilder { - self.holder.apiserverHost = apiServerHost +func (self *holderBuilder) SetApiserverHost(apiserverHost string) *holderBuilder { + self.holder.apiserverHost = apiserverHost return self } @@ -114,9 +114,9 @@ func (self *holderBuilder) SetSidecarHost(sidecarHost string) *holderBuilder { return self } -// SetKubeConfigFile 'kubeconfig' argument of Dashboard binary. -func (self *holderBuilder) SetKubeConfigFile(kubeConfigFile string) *holderBuilder { - self.holder.kubeConfigFile = kubeConfigFile +// SetKubeconfigFile 'kubeconfig' argument of Dashboard binary. +func (self *holderBuilder) SetKubeconfigFile(kubeconfigFile string) *holderBuilder { + self.holder.kubeconfigFile = kubeconfigFile return self } diff --git a/modules/api/pkg/args/holder.go b/modules/api/pkg/args/holder.go index af1957c0c035..125d72c20f3e 100644 --- a/modules/api/pkg/args/holder.go +++ b/modules/api/pkg/args/holder.go @@ -42,7 +42,7 @@ type holder struct { metricsProvider string heapsterHost string sidecarHost string - kubeConfigFile string + kubeconfigFile string apiLogLevel string namespace string @@ -138,9 +138,9 @@ func (self *holder) GetSidecarHost() string { return self.sidecarHost } -// GetKubeConfigFile 'kubeconfig' argument of Dashboard binary. -func (self *holder) GetKubeConfigFile() string { - return self.kubeConfigFile +// GetKubeconfigFile 'kubeconfig' argument of Dashboard binary. +func (self *holder) GetKubeconfigFile() string { + return self.kubeconfigFile } // GetAPILogLevel 'api-log-level' argument of Dashboard binary. diff --git a/modules/api/pkg/client/manager.go b/modules/api/pkg/client/manager.go index 6b02ffc3b57b..6dc38f3bc5fa 100644 --- a/modules/api/pkg/client/manager.go +++ b/modules/api/pkg/client/manager.go @@ -17,6 +17,7 @@ package client import ( "context" "log" + "os" "regexp" "strings" @@ -71,9 +72,9 @@ var Version = "UNKNOWN" type clientManager struct { // Autogenerated key on backend start used to secure requests from csrf attacks csrfKey string - // Path to kubeconfig file. If both kubeConfigPath and apiserverHost are empty + // Path to kubeconfig file. If both kubeconfigPath and apiserverHost are empty // inClusterConfig will be used - kubeConfigPath string + kubeconfigPath string // Address of apiserver host in format 'protocol://address:port' apiserverHost string // Initialized on clientManager creation and used if kubeconfigPath and apiserverHost are @@ -214,7 +215,7 @@ func (self *clientManager) ClientCmdConfig(req *restful.Request) (clientcmd.Clie return nil, err } - cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeConfigPath) + cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeconfigPath) if err != nil { return nil, err } @@ -230,7 +231,7 @@ func (self *clientManager) CSRFKey() string { // HasAccess configures K8S api client with provided auth info and executes a basic check against apiserver to see // if it is valid. func (self *clientManager) HasAccess(authInfo api.AuthInfo) (string, error) { - cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeConfigPath) + cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeconfigPath) if err != nil { return "", err } @@ -320,7 +321,7 @@ func (self *clientManager) initConfig(cfg *rest.Config) { cfg.UserAgent = DefaultUserAgent + "/" + Version } -// Returns rest Config based on provided apiserverHost and kubeConfigPath flags. If both are +// Returns rest Config based on provided apiserverHost and kubeconfigPath flags. If both are // empty then in-cluster config will be used and if it is nil the error is returned. func (self *clientManager) buildConfigFromFlags(apiserverHost, kubeConfigPath string) ( *rest.Config, error) { @@ -367,6 +368,12 @@ func (self *clientManager) extractAuthInfo(req *restful.Request) (*api.AuthInfo, // Authorization header will be more important than our token token := self.extractTokenFromHeader(authHeader) if len(token) > 0 || len(impersonationHeader) > 0 { + if token == "" { + // If Impersonate* header(s) provided, but Authorization token is missing, + // fallback to in-cluster config by reading the serviceaccount token from the filesystem + var dat, _ = os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") + token = string(dat) + } authInfo := &api.AuthInfo{Token: token} if len(impersonationHeader) > 0 { @@ -505,9 +512,9 @@ func (self *clientManager) init() { self.initCSRFKey() } -// Initializes in-cluster config if apiserverHost and kubeConfigPath were not provided. +// Initializes in-cluster config if apiserverHost and kubeconfigPath were not provided. func (self *clientManager) initInClusterConfig() { - if len(self.apiserverHost) > 0 || len(self.kubeConfigPath) > 0 { + if len(self.apiserverHost) > 0 || len(self.kubeconfigPath) > 0 { log.Print("Skipping in-cluster config") return } @@ -561,7 +568,7 @@ func (self *clientManager) initInsecureClients() { } func (self *clientManager) initInsecureConfig() { - cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeConfigPath) + cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeconfigPath) if err != nil { panic(err) } @@ -597,7 +604,7 @@ func (self *clientManager) getUsername(name string) string { // If both are empty then in-cluster config is used. func NewClientManager(kubeConfigPath, apiserverHost string) clientapi.ClientManager { result := &clientManager{ - kubeConfigPath: kubeConfigPath, + kubeconfigPath: kubeConfigPath, apiserverHost: apiserverHost, } diff --git a/modules/api/pkg/integration/manager_test.go b/modules/api/pkg/integration/manager_test.go index 4651e506d1d0..a523dcf7083c 100644 --- a/modules/api/pkg/integration/manager_test.go +++ b/modules/api/pkg/integration/manager_test.go @@ -44,7 +44,7 @@ func TestNewIntegrationManager(t *testing.T) { func TestIntegrationManager_GetState(t *testing.T) { cases := []struct { info string - apiServerHost string + apiserverHost string sidecarHost string expected *api.IntegrationState expectedErr error @@ -59,7 +59,7 @@ func TestIntegrationManager_GetState(t *testing.T) { } for _, c := range cases { - cManager := client.NewClientManager("", c.apiServerHost) + cManager := client.NewClientManager("", c.apiserverHost) iManager := NewIntegrationManager(cManager) iManager.Metric().ConfigureSidecar(c.sidecarHost)