From 5ff6b77e22dd0dbc9205facb2c1b0f4293eb26be Mon Sep 17 00:00:00 2001 From: Jonathan Hess Date: Tue, 18 Jul 2023 12:34:13 -0600 Subject: [PATCH] feat: Add support for Service Account Impersonation. --- ...l.cloud.google.com_authproxyworkloads.yaml | 9 +++++ docs/api.md | 15 ++++++++ installer/cloud-sql-proxy-operator.yaml | 9 +++++ internal/api/v1/authproxyworkload_test.go | 9 +++++ internal/api/v1/authproxyworkload_types.go | 17 +++++++++ internal/api/v1/zz_generated.deepcopy.go | 25 +++++++++++++ internal/workload/podspec_updates.go | 14 ++++++++ internal/workload/podspec_updates_test.go | 36 ++++++++++--------- 8 files changed, 118 insertions(+), 16 deletions(-) diff --git a/config/crd/bases/cloudsql.cloud.google.com_authproxyworkloads.yaml b/config/crd/bases/cloudsql.cloud.google.com_authproxyworkloads.yaml index a2a91e19..1a991108 100644 --- a/config/crd/bases/cloudsql.cloud.google.com_authproxyworkloads.yaml +++ b/config/crd/bases/cloudsql.cloud.google.com_authproxyworkloads.yaml @@ -60,6 +60,15 @@ spec: minimum: 1 type: integer type: object + authentication: + description: Authentication specifies the config for how the proxy authenticates itself to the Google Cloud API. + properties: + impersonationChain: + description: ImpersonationChain is a list of one or more service accounts. The first entry in the chain is the impersonation target. Any additional service accounts after the target are delegates. The roles/iam.serviceAccountTokenCreator must be configured for each account that will be impersonated. This sets the --impersonate-service-account flag on the proxy. + items: + type: string + type: array + type: object container: description: Container is debugging parameter that when specified will override the proxy container with a completely custom Container spec. properties: diff --git a/docs/api.md b/docs/api.md index 3d58b93b..5099bad4 100644 --- a/docs/api.md +++ b/docs/api.md @@ -45,6 +45,7 @@ _Appears in:_ | `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#resourcerequirements-v1-core)_ | Resources specifies the resources required for the proxy pod. | | `telemetry` _[TelemetrySpec](#telemetryspec)_ | Telemetry specifies how the proxy should expose telemetry. Optional, by default | | `adminServer` _[AdminServerSpec](#adminserverspec)_ | AdminServer specifies the config for the proxy's admin service which is available to other containers in the same pod. | +| `authentication` _[AuthenticationSpec](#authenticationspec)_ | Authentication specifies the config for how the proxy authenticates itself to the Google Cloud API. | | `maxConnections` _integer_ | MaxConnections limits the number of connections. Default value is no limit. This sets the proxy container's CLI argument `--max-connections` | | `maxSigtermDelay` _integer_ | MaxSigtermDelay is the maximum number of seconds to wait for connections to close after receiving a TERM signal. This sets the proxy container's CLI argument `--max-sigterm-delay` and configures `terminationGracePeriodSeconds` on the workload's PodSpec. | | `sqlAdminAPIEndpoint` _string_ | SQLAdminAPIEndpoint is a debugging parameter that when specified will change the Google Cloud api endpoint used by the proxy. | @@ -86,6 +87,20 @@ _Appears in:_ | `authProxyContainer` _[AuthProxyContainerSpec](#authproxycontainerspec)_ | AuthProxyContainer describes the resources and config for the Auth Proxy container. | +#### AuthenticationSpec + + + +AuthenticationSpec specifies how the proxy is authenticated with the Google Cloud SQL Admin API. This configures proxy's --impersonate-service-account flag. + +_Appears in:_ +- [AuthProxyContainerSpec](#authproxycontainerspec) + +| Field | Description | +| --- | --- | +| `impersonationChain` _string array_ | ImpersonationChain is a list of one or more service accounts. The first entry in the chain is the impersonation target. Any additional service accounts after the target are delegates. The roles/iam.serviceAccountTokenCreator must be configured for each account that will be impersonated. This sets the --impersonate-service-account flag on the proxy. | + + #### InstanceSpec diff --git a/installer/cloud-sql-proxy-operator.yaml b/installer/cloud-sql-proxy-operator.yaml index 56cd3855..073e70fd 100644 --- a/installer/cloud-sql-proxy-operator.yaml +++ b/installer/cloud-sql-proxy-operator.yaml @@ -79,6 +79,15 @@ spec: minimum: 1 type: integer type: object + authentication: + description: Authentication specifies the config for how the proxy authenticates itself to the Google Cloud API. + properties: + impersonationChain: + description: ImpersonationChain is a list of one or more service accounts. The first entry in the chain is the impersonation target. Any additional service accounts after the target are delegates. The roles/iam.serviceAccountTokenCreator must be configured for each account that will be impersonated. This sets the --impersonate-service-account flag on the proxy. + items: + type: string + type: array + type: object container: description: Container is debugging parameter that when specified will override the proxy container with a completely custom Container spec. properties: diff --git a/internal/api/v1/authproxyworkload_test.go b/internal/api/v1/authproxyworkload_test.go index 31fe5d00..5370269d 100644 --- a/internal/api/v1/authproxyworkload_test.go +++ b/internal/api/v1/authproxyworkload_test.go @@ -237,6 +237,15 @@ func TestAuthProxyWorkload_ValidateCreate_AuthProxyContainerSpec(t *testing.T) { }, wantValid: true, }, + { + desc: "Valid, ImpersonationChain set", + spec: cloudsqlapi.AuthProxyContainerSpec{ + Authentication: &cloudsqlapi.AuthenticationSpec{ + ImpersonationChain: []string{"sv1@developer.gserviceaccount.com", "sv2@developer.gserviceaccount.com"}, + }, + }, + wantValid: true, + }, { desc: "Invalid, Debug set without AdminPort", spec: cloudsqlapi.AuthProxyContainerSpec{ diff --git a/internal/api/v1/authproxyworkload_types.go b/internal/api/v1/authproxyworkload_types.go index d40932bc..e131b62a 100644 --- a/internal/api/v1/authproxyworkload_types.go +++ b/internal/api/v1/authproxyworkload_types.go @@ -159,6 +159,10 @@ type AuthProxyContainerSpec struct { // available to other containers in the same pod. AdminServer *AdminServerSpec `json:"adminServer,omitempty"` + // Authentication specifies the config for how the proxy authenticates itself + // to the Google Cloud API. + Authentication *AuthenticationSpec `json:"authentication,omitempty"` + // MaxConnections limits the number of connections. Default value is no limit. // This sets the proxy container's CLI argument `--max-connections` //+kubebuilder:validation:Optional @@ -227,6 +231,19 @@ type AdminServerSpec struct { EnableAPIs []string `json:"enableAPIs,omitempty"` } +// AuthenticationSpec specifies how the proxy is authenticated with the +// Google Cloud SQL Admin API. This configures proxy's +// --impersonate-service-account flag. +type AuthenticationSpec struct { + // ImpersonationChain is a list of one or more service + // accounts. The first entry in the chain is the impersonation target. Any + // additional service accounts after the target are delegates. The + // roles/iam.serviceAccountTokenCreator must be configured for each account + // that will be impersonated. This sets the --impersonate-service-account + // flag on the proxy. + ImpersonationChain []string `json:"impersonationChain,omitempty"` +} + // TelemetrySpec specifies how the proxy container will expose telemetry. type TelemetrySpec struct { // QuotaProject Specifies the project to use for Cloud SQL Admin API quota tracking. diff --git a/internal/api/v1/zz_generated.deepcopy.go b/internal/api/v1/zz_generated.deepcopy.go index 2c6bde5e..7ccb31e0 100644 --- a/internal/api/v1/zz_generated.deepcopy.go +++ b/internal/api/v1/zz_generated.deepcopy.go @@ -67,6 +67,11 @@ func (in *AuthProxyContainerSpec) DeepCopyInto(out *AuthProxyContainerSpec) { *out = new(AdminServerSpec) (*in).DeepCopyInto(*out) } + if in.Authentication != nil { + in, out := &in.Authentication, &out.Authentication + *out = new(AuthenticationSpec) + (*in).DeepCopyInto(*out) + } if in.MaxConnections != nil { in, out := &in.MaxConnections, &out.MaxConnections *out = new(int64) @@ -213,6 +218,26 @@ func (in *AuthProxyWorkloadStatus) DeepCopy() *AuthProxyWorkloadStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthenticationSpec) DeepCopyInto(out *AuthenticationSpec) { + *out = *in + if in.ImpersonationChain != nil { + in, out := &in.ImpersonationChain, &out.ImpersonationChain + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthenticationSpec. +func (in *AuthenticationSpec) DeepCopy() *AuthenticationSpec { + if in == nil { + return nil + } + out := new(AuthenticationSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InstanceSpec) DeepCopyInto(out *InstanceSpec) { *out = *in diff --git a/internal/workload/podspec_updates.go b/internal/workload/podspec_updates.go index 958b5e64..39560108 100644 --- a/internal/workload/podspec_updates.go +++ b/internal/workload/podspec_updates.go @@ -590,6 +590,9 @@ func (s *updateState) updateContainer(p *cloudsqlapi.AuthProxyWorkload, c *corev // enable the proxy's admin service s.addAdminServer(p) + // configure container authentication + s.addAuthentication(p) + // add the user agent s.addProxyContainerEnvVar(p, "CSQL_PROXY_USER_AGENT", s.updater.userAgent) @@ -881,6 +884,17 @@ func (s *updateState) addAdminServer(p *cloudsqlapi.AuthProxyWorkload) { } +func (s *updateState) addAuthentication(p *cloudsqlapi.AuthProxyWorkload) { + if p.Spec.AuthProxyContainer == nil || p.Spec.AuthProxyContainer.Authentication == nil { + return + } + as := p.Spec.AuthProxyContainer.Authentication + if len(as.ImpersonationChain) > 0 { + s.addProxyContainerEnvVar(p, "CSQL_PROXY_IMPERSONATE_SERVICE_ACCOUNT", strings.Join(as.ImpersonationChain, ",")) + } + +} + func (s *updateState) addVolumeMount(p *cloudsqlapi.AuthProxyWorkload, is *cloudsqlapi.InstanceSpec, m corev1.VolumeMount, v corev1.Volume) { key := proxyInstanceID{ AuthProxyWorkload: types.NamespacedName{ diff --git a/internal/workload/podspec_updates_test.go b/internal/workload/podspec_updates_test.go index 0fdb636c..f5248cbd 100644 --- a/internal/workload/podspec_updates_test.go +++ b/internal/workload/podspec_updates_test.go @@ -664,6 +664,9 @@ func TestProxyCLIArgs(t *testing.T) { EnableAPIs: []string{"Debug", "QuitQuitQuit"}, Port: int32(9091), }, + Authentication: &cloudsqlapi.AuthenticationSpec{ + ImpersonationChain: []string{"sv1@developer.gserviceaccount.com", "sv2@developer.gserviceaccount.com"}, + }, MaxConnections: ptr(int64(10)), MaxSigtermDelay: ptr(int64(20)), }, @@ -676,22 +679,23 @@ func TestProxyCLIArgs(t *testing.T) { fmt.Sprintf("hello:world:one?port=%d", workload.DefaultFirstPort), }, wantWorkloadEnv: map[string]string{ - "CSQL_PROXY_SQLADMIN_API_ENDPOINT": "https://example.com", - "CSQL_PROXY_TELEMETRY_SAMPLE_RATE": "200", - "CSQL_PROXY_PROMETHEUS_NAMESPACE": "hello", - "CSQL_PROXY_TELEMETRY_PROJECT": "telproject", - "CSQL_PROXY_TELEMETRY_PREFIX": "telprefix", - "CSQL_PROXY_HTTP_PORT": "9092", - "CSQL_PROXY_ADMIN_PORT": "9091", - "CSQL_PROXY_DEBUG": "true", - "CSQL_PROXY_QUITQUITQUIT": "true", - "CSQL_PROXY_HEALTH_CHECK": "true", - "CSQL_PROXY_DISABLE_TRACES": "true", - "CSQL_PROXY_DISABLE_METRICS": "true", - "CSQL_PROXY_PROMETHEUS": "true", - "CSQL_PROXY_QUOTA_PROJECT": "qp", - "CSQL_PROXY_MAX_CONNECTIONS": "10", - "CSQL_PROXY_MAX_SIGTERM_DELAY": "20", + "CSQL_PROXY_SQLADMIN_API_ENDPOINT": "https://example.com", + "CSQL_PROXY_TELEMETRY_SAMPLE_RATE": "200", + "CSQL_PROXY_PROMETHEUS_NAMESPACE": "hello", + "CSQL_PROXY_TELEMETRY_PROJECT": "telproject", + "CSQL_PROXY_TELEMETRY_PREFIX": "telprefix", + "CSQL_PROXY_HTTP_PORT": "9092", + "CSQL_PROXY_ADMIN_PORT": "9091", + "CSQL_PROXY_DEBUG": "true", + "CSQL_PROXY_QUITQUITQUIT": "true", + "CSQL_PROXY_HEALTH_CHECK": "true", + "CSQL_PROXY_DISABLE_TRACES": "true", + "CSQL_PROXY_DISABLE_METRICS": "true", + "CSQL_PROXY_PROMETHEUS": "true", + "CSQL_PROXY_QUOTA_PROJECT": "qp", + "CSQL_PROXY_MAX_CONNECTIONS": "10", + "CSQL_PROXY_MAX_SIGTERM_DELAY": "20", + "CSQL_PROXY_IMPERSONATE_SERVICE_ACCOUNT": "sv1@developer.gserviceaccount.com,sv2@developer.gserviceaccount.com", }, }, {