From 4a456446ffd6471ee7b098a76817fabd72bcbcfc Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Tue, 11 May 2021 21:23:43 -0500 Subject: [PATCH 01/43] Update doc comments for types_credentialissuer.go.tmpl. Update to follow https://golang.org/doc/effective_go#commentary: > The first sentence should be a one-sentence summary that starts with the name being declared. Signed-off-by: Matt Moyer --- .../v1alpha1/types_credentialissuer.go.tmpl | 16 +++++++++++----- ...concierge.pinniped.dev_credentialissuers.yaml | 7 ++++--- generated/1.17/README.adoc | 4 ++-- .../config/v1alpha1/types_credentialissuer.go | 16 +++++++++++----- ...concierge.pinniped.dev_credentialissuers.yaml | 7 ++++--- generated/1.18/README.adoc | 4 ++-- .../config/v1alpha1/types_credentialissuer.go | 16 +++++++++++----- ...concierge.pinniped.dev_credentialissuers.yaml | 7 ++++--- generated/1.19/README.adoc | 4 ++-- .../config/v1alpha1/types_credentialissuer.go | 16 +++++++++++----- ...concierge.pinniped.dev_credentialissuers.yaml | 7 ++++--- generated/1.20/README.adoc | 4 ++-- .../config/v1alpha1/types_credentialissuer.go | 16 +++++++++++----- ...concierge.pinniped.dev_credentialissuers.yaml | 7 ++++--- .../config/v1alpha1/types_credentialissuer.go | 16 +++++++++++----- 15 files changed, 94 insertions(+), 53 deletions(-) diff --git a/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl b/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl index 3c0c8ba0..4a6607c5 100644 --- a/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl +++ b/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl @@ -5,15 +5,19 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy type StrategyType string +// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster. // +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy type FrontendType string +// StrategyStatus enumerates whether a strategy is working on a cluster. // +kubebuilder:validation:Enum=Success;Error type StrategyStatus string +// StrategyReason enumerates the detailed reason why a strategy is in a particular status. // +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey type StrategyReason string @@ -36,7 +40,7 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) -// Status of a credential issuer. +// CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. Strategies []CredentialIssuerStrategy `json:"strategies"` @@ -47,7 +51,8 @@ type CredentialIssuerStatus struct { KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"` } -// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// CredentialIssuerKubeConfigInfo provides the information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// This type is deprecated and will be removed in a future version. type CredentialIssuerKubeConfigInfo struct { // The K8s API server URL. // +kubebuilder:validation:MinLength=1 @@ -59,7 +64,7 @@ type CredentialIssuerKubeConfigInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Status of an integration strategy that was attempted by Pinniped. +// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped. type CredentialIssuerStrategy struct { // Type of integration attempted. Type StrategyType `json:"type"` @@ -81,6 +86,7 @@ type CredentialIssuerStrategy struct { Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"` } +// CredentialIssuerFrontend describes how to connect using a particular integration strategy. type CredentialIssuerFrontend struct { // Type describes which frontend mechanism clients can use with a strategy. Type FrontendType `json:"type"` @@ -118,7 +124,7 @@ type ImpersonationProxyInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Describes the configuration status of a Pinniped credential issuer. +// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -133,7 +139,7 @@ type CredentialIssuer struct { Status CredentialIssuerStatus `json:"status"` } -// List of CredentialIssuer objects. +// CredentialIssuerList is a list of CredentialIssuer objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type CredentialIssuerList struct { metav1.TypeMeta `json:",inline"` diff --git a/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml b/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml index 8123f238..992331de 100644 --- a/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml @@ -21,7 +21,8 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Describes the configuration status of a Pinniped credential issuer. + description: CredentialIssuer describes the configuration and status of the + Pinniped Concierge credential issuer. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -60,8 +61,8 @@ spec: description: List of integration strategies that were attempted by Pinniped. items: - description: Status of an integration strategy that was attempted - by Pinniped. + description: CredentialIssuerStrategy describes the status of an + integration strategy that was attempted by Pinniped. properties: frontend: description: Frontend describes how clients can connect using diff --git a/generated/1.17/README.adoc b/generated/1.17/README.adoc index edda79aa..67ca2720 100644 --- a/generated/1.17/README.adoc +++ b/generated/1.17/README.adoc @@ -220,7 +220,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuer"] ==== CredentialIssuer -Describes the configuration status of a Pinniped credential issuer. +CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. .Appears In: **** @@ -278,7 +278,7 @@ Describes the configuration status of a Pinniped credential issuer. [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus -Status of a credential issuer. +CredentialIssuerStatus describes the status of the Concierge. .Appears In: **** diff --git a/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go index 3c0c8ba0..4a6607c5 100644 --- a/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -5,15 +5,19 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy type StrategyType string +// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster. // +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy type FrontendType string +// StrategyStatus enumerates whether a strategy is working on a cluster. // +kubebuilder:validation:Enum=Success;Error type StrategyStatus string +// StrategyReason enumerates the detailed reason why a strategy is in a particular status. // +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey type StrategyReason string @@ -36,7 +40,7 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) -// Status of a credential issuer. +// CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. Strategies []CredentialIssuerStrategy `json:"strategies"` @@ -47,7 +51,8 @@ type CredentialIssuerStatus struct { KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"` } -// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// CredentialIssuerKubeConfigInfo provides the information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// This type is deprecated and will be removed in a future version. type CredentialIssuerKubeConfigInfo struct { // The K8s API server URL. // +kubebuilder:validation:MinLength=1 @@ -59,7 +64,7 @@ type CredentialIssuerKubeConfigInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Status of an integration strategy that was attempted by Pinniped. +// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped. type CredentialIssuerStrategy struct { // Type of integration attempted. Type StrategyType `json:"type"` @@ -81,6 +86,7 @@ type CredentialIssuerStrategy struct { Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"` } +// CredentialIssuerFrontend describes how to connect using a particular integration strategy. type CredentialIssuerFrontend struct { // Type describes which frontend mechanism clients can use with a strategy. Type FrontendType `json:"type"` @@ -118,7 +124,7 @@ type ImpersonationProxyInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Describes the configuration status of a Pinniped credential issuer. +// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -133,7 +139,7 @@ type CredentialIssuer struct { Status CredentialIssuerStatus `json:"status"` } -// List of CredentialIssuer objects. +// CredentialIssuerList is a list of CredentialIssuer objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type CredentialIssuerList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 8123f238..992331de 100644 --- a/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -21,7 +21,8 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Describes the configuration status of a Pinniped credential issuer. + description: CredentialIssuer describes the configuration and status of the + Pinniped Concierge credential issuer. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -60,8 +61,8 @@ spec: description: List of integration strategies that were attempted by Pinniped. items: - description: Status of an integration strategy that was attempted - by Pinniped. + description: CredentialIssuerStrategy describes the status of an + integration strategy that was attempted by Pinniped. properties: frontend: description: Frontend describes how clients can connect using diff --git a/generated/1.18/README.adoc b/generated/1.18/README.adoc index cc33968f..5f9555d9 100644 --- a/generated/1.18/README.adoc +++ b/generated/1.18/README.adoc @@ -220,7 +220,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuer"] ==== CredentialIssuer -Describes the configuration status of a Pinniped credential issuer. +CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. .Appears In: **** @@ -278,7 +278,7 @@ Describes the configuration status of a Pinniped credential issuer. [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus -Status of a credential issuer. +CredentialIssuerStatus describes the status of the Concierge. .Appears In: **** diff --git a/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go index 3c0c8ba0..4a6607c5 100644 --- a/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -5,15 +5,19 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy type StrategyType string +// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster. // +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy type FrontendType string +// StrategyStatus enumerates whether a strategy is working on a cluster. // +kubebuilder:validation:Enum=Success;Error type StrategyStatus string +// StrategyReason enumerates the detailed reason why a strategy is in a particular status. // +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey type StrategyReason string @@ -36,7 +40,7 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) -// Status of a credential issuer. +// CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. Strategies []CredentialIssuerStrategy `json:"strategies"` @@ -47,7 +51,8 @@ type CredentialIssuerStatus struct { KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"` } -// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// CredentialIssuerKubeConfigInfo provides the information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// This type is deprecated and will be removed in a future version. type CredentialIssuerKubeConfigInfo struct { // The K8s API server URL. // +kubebuilder:validation:MinLength=1 @@ -59,7 +64,7 @@ type CredentialIssuerKubeConfigInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Status of an integration strategy that was attempted by Pinniped. +// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped. type CredentialIssuerStrategy struct { // Type of integration attempted. Type StrategyType `json:"type"` @@ -81,6 +86,7 @@ type CredentialIssuerStrategy struct { Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"` } +// CredentialIssuerFrontend describes how to connect using a particular integration strategy. type CredentialIssuerFrontend struct { // Type describes which frontend mechanism clients can use with a strategy. Type FrontendType `json:"type"` @@ -118,7 +124,7 @@ type ImpersonationProxyInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Describes the configuration status of a Pinniped credential issuer. +// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -133,7 +139,7 @@ type CredentialIssuer struct { Status CredentialIssuerStatus `json:"status"` } -// List of CredentialIssuer objects. +// CredentialIssuerList is a list of CredentialIssuer objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type CredentialIssuerList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 8123f238..992331de 100644 --- a/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -21,7 +21,8 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Describes the configuration status of a Pinniped credential issuer. + description: CredentialIssuer describes the configuration and status of the + Pinniped Concierge credential issuer. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -60,8 +61,8 @@ spec: description: List of integration strategies that were attempted by Pinniped. items: - description: Status of an integration strategy that was attempted - by Pinniped. + description: CredentialIssuerStrategy describes the status of an + integration strategy that was attempted by Pinniped. properties: frontend: description: Frontend describes how clients can connect using diff --git a/generated/1.19/README.adoc b/generated/1.19/README.adoc index d0effe28..8f16ea55 100644 --- a/generated/1.19/README.adoc +++ b/generated/1.19/README.adoc @@ -220,7 +220,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuer"] ==== CredentialIssuer -Describes the configuration status of a Pinniped credential issuer. +CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. .Appears In: **** @@ -278,7 +278,7 @@ Describes the configuration status of a Pinniped credential issuer. [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus -Status of a credential issuer. +CredentialIssuerStatus describes the status of the Concierge. .Appears In: **** diff --git a/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go index 3c0c8ba0..4a6607c5 100644 --- a/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -5,15 +5,19 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy type StrategyType string +// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster. // +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy type FrontendType string +// StrategyStatus enumerates whether a strategy is working on a cluster. // +kubebuilder:validation:Enum=Success;Error type StrategyStatus string +// StrategyReason enumerates the detailed reason why a strategy is in a particular status. // +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey type StrategyReason string @@ -36,7 +40,7 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) -// Status of a credential issuer. +// CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. Strategies []CredentialIssuerStrategy `json:"strategies"` @@ -47,7 +51,8 @@ type CredentialIssuerStatus struct { KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"` } -// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// CredentialIssuerKubeConfigInfo provides the information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// This type is deprecated and will be removed in a future version. type CredentialIssuerKubeConfigInfo struct { // The K8s API server URL. // +kubebuilder:validation:MinLength=1 @@ -59,7 +64,7 @@ type CredentialIssuerKubeConfigInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Status of an integration strategy that was attempted by Pinniped. +// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped. type CredentialIssuerStrategy struct { // Type of integration attempted. Type StrategyType `json:"type"` @@ -81,6 +86,7 @@ type CredentialIssuerStrategy struct { Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"` } +// CredentialIssuerFrontend describes how to connect using a particular integration strategy. type CredentialIssuerFrontend struct { // Type describes which frontend mechanism clients can use with a strategy. Type FrontendType `json:"type"` @@ -118,7 +124,7 @@ type ImpersonationProxyInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Describes the configuration status of a Pinniped credential issuer. +// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -133,7 +139,7 @@ type CredentialIssuer struct { Status CredentialIssuerStatus `json:"status"` } -// List of CredentialIssuer objects. +// CredentialIssuerList is a list of CredentialIssuer objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type CredentialIssuerList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 8123f238..992331de 100644 --- a/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -21,7 +21,8 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Describes the configuration status of a Pinniped credential issuer. + description: CredentialIssuer describes the configuration and status of the + Pinniped Concierge credential issuer. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -60,8 +61,8 @@ spec: description: List of integration strategies that were attempted by Pinniped. items: - description: Status of an integration strategy that was attempted - by Pinniped. + description: CredentialIssuerStrategy describes the status of an + integration strategy that was attempted by Pinniped. properties: frontend: description: Frontend describes how clients can connect using diff --git a/generated/1.20/README.adoc b/generated/1.20/README.adoc index 348c55d0..0bbef389 100644 --- a/generated/1.20/README.adoc +++ b/generated/1.20/README.adoc @@ -220,7 +220,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration [id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuer"] ==== CredentialIssuer -Describes the configuration status of a Pinniped credential issuer. +CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. .Appears In: **** @@ -278,7 +278,7 @@ Describes the configuration status of a Pinniped credential issuer. [id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus -Status of a credential issuer. +CredentialIssuerStatus describes the status of the Concierge. .Appears In: **** diff --git a/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go index 3c0c8ba0..4a6607c5 100644 --- a/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -5,15 +5,19 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy type StrategyType string +// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster. // +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy type FrontendType string +// StrategyStatus enumerates whether a strategy is working on a cluster. // +kubebuilder:validation:Enum=Success;Error type StrategyStatus string +// StrategyReason enumerates the detailed reason why a strategy is in a particular status. // +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey type StrategyReason string @@ -36,7 +40,7 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) -// Status of a credential issuer. +// CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. Strategies []CredentialIssuerStrategy `json:"strategies"` @@ -47,7 +51,8 @@ type CredentialIssuerStatus struct { KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"` } -// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// CredentialIssuerKubeConfigInfo provides the information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// This type is deprecated and will be removed in a future version. type CredentialIssuerKubeConfigInfo struct { // The K8s API server URL. // +kubebuilder:validation:MinLength=1 @@ -59,7 +64,7 @@ type CredentialIssuerKubeConfigInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Status of an integration strategy that was attempted by Pinniped. +// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped. type CredentialIssuerStrategy struct { // Type of integration attempted. Type StrategyType `json:"type"` @@ -81,6 +86,7 @@ type CredentialIssuerStrategy struct { Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"` } +// CredentialIssuerFrontend describes how to connect using a particular integration strategy. type CredentialIssuerFrontend struct { // Type describes which frontend mechanism clients can use with a strategy. Type FrontendType `json:"type"` @@ -118,7 +124,7 @@ type ImpersonationProxyInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Describes the configuration status of a Pinniped credential issuer. +// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -133,7 +139,7 @@ type CredentialIssuer struct { Status CredentialIssuerStatus `json:"status"` } -// List of CredentialIssuer objects. +// CredentialIssuerList is a list of CredentialIssuer objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type CredentialIssuerList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 8123f238..992331de 100644 --- a/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -21,7 +21,8 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Describes the configuration status of a Pinniped credential issuer. + description: CredentialIssuer describes the configuration and status of the + Pinniped Concierge credential issuer. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -60,8 +61,8 @@ spec: description: List of integration strategies that were attempted by Pinniped. items: - description: Status of an integration strategy that was attempted - by Pinniped. + description: CredentialIssuerStrategy describes the status of an + integration strategy that was attempted by Pinniped. properties: frontend: description: Frontend describes how clients can connect using diff --git a/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go index 3c0c8ba0..4a6607c5 100644 --- a/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -5,15 +5,19 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy type StrategyType string +// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster. // +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy type FrontendType string +// StrategyStatus enumerates whether a strategy is working on a cluster. // +kubebuilder:validation:Enum=Success;Error type StrategyStatus string +// StrategyReason enumerates the detailed reason why a strategy is in a particular status. // +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey type StrategyReason string @@ -36,7 +40,7 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) -// Status of a credential issuer. +// CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. Strategies []CredentialIssuerStrategy `json:"strategies"` @@ -47,7 +51,8 @@ type CredentialIssuerStatus struct { KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"` } -// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// CredentialIssuerKubeConfigInfo provides the information needed to form a valid Pinniped-based kubeconfig using this credential issuer. +// This type is deprecated and will be removed in a future version. type CredentialIssuerKubeConfigInfo struct { // The K8s API server URL. // +kubebuilder:validation:MinLength=1 @@ -59,7 +64,7 @@ type CredentialIssuerKubeConfigInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Status of an integration strategy that was attempted by Pinniped. +// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped. type CredentialIssuerStrategy struct { // Type of integration attempted. Type StrategyType `json:"type"` @@ -81,6 +86,7 @@ type CredentialIssuerStrategy struct { Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"` } +// CredentialIssuerFrontend describes how to connect using a particular integration strategy. type CredentialIssuerFrontend struct { // Type describes which frontend mechanism clients can use with a strategy. Type FrontendType `json:"type"` @@ -118,7 +124,7 @@ type ImpersonationProxyInfo struct { CertificateAuthorityData string `json:"certificateAuthorityData"` } -// Describes the configuration status of a Pinniped credential issuer. +// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer. // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -133,7 +139,7 @@ type CredentialIssuer struct { Status CredentialIssuerStatus `json:"status"` } -// List of CredentialIssuer objects. +// CredentialIssuerList is a list of CredentialIssuer objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type CredentialIssuerList struct { metav1.TypeMeta `json:",inline"` From 26da7639620ec7eb5af38432759e5821f065ea56 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Fri, 14 May 2021 10:19:45 -0500 Subject: [PATCH 02/43] Add spec fields to CredentialIssuer. Signed-off-by: Matt Moyer --- .../v1alpha1/types_credentialissuer.go.tmpl | 102 +++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl b/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl index 4a6607c5..c102c1a8 100644 --- a/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl +++ b/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl @@ -3,7 +3,9 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy @@ -40,6 +42,95 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) +// CredentialIssuerSpec describes the intended configuration of the Concierge. +type CredentialIssuerSpec struct { + // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. + // + //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} + ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` +} + +// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. +// +// +kubebuilder:validation:Enum=auto;enabled;disabled +type ImpersonationProxyMode string + +const ( + // ImpersonationProxyModeDisabled explicitly disables the impersonation proxy. + ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled") + + // ImpersonationProxyModeEnabled explicitly enables the impersonation proxy. + ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled") + + // ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running. + ImpersonationProxyModeAuto = ImpersonationProxyMode("auto") +) + +// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy. +// +// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None +type ImpersonationProxyServiceType string + +const ( + // ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer. + ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer") + + // ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP. + ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP") + + // ImpersonationProxyServiceTypeNone does not automatically provision any service. + ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") +) + +// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. +type ImpersonationProxySpec struct { + // Mode configures whether the impersonation proxy should be started: + // - "disabled" explicitly disables the impersonation proxy. This is the default. + // - "enabled" explicitly enables the impersonation proxy. + // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. + // + // +kubebuilder:default:="disabled" + Mode ImpersonationProxyMode `json:"mode"` + + // Service describes the configuraiton + // + // +kubebuilder:default:={"type": "LoadBalancer"} + Service ImpersonationProxyServiceSpec `json:"service"` + + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this + // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be + // advertised. + // + // Setting this field disables the automatic creation of this LoadBalancer Service. + // + // +optional + ExternalEndpoint string `json:"externalEndpoint,omitempty"` +} + +// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. +type ImpersonationProxyServiceSpec struct { + // Type specifies the type of Service to provision for the impersonation proxy. + // + // If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty + // value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. + // + // +kubebuilder:default:="LoadBalancer" + Type ImpersonationProxyServiceType `json:"type,omitempty"` + + // LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. + // This is not supported on all cloud providers. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +optional + LoadBalancerIP string `json:"loadBalancerIP,omitempty"` + + // Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. + // + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. @@ -134,7 +225,14 @@ type CredentialIssuer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Status of the credential issuer. + // Spec describes the intended configuration of the Concierge. + // + // +optional + // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} + Spec CredentialIssuerSpec `json:"spec"` + + // CredentialIssuerStatus describes the status of the Concierge. + // // +optional Status CredentialIssuerStatus `json:"status"` } From e8851142213e618d1cb4874b4dea9f8f62978429 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Fri, 14 May 2021 11:55:12 -0500 Subject: [PATCH 03/43] Add generated code from adding spec fields to CredentialIssuer. Signed-off-by: Matt Moyer --- ...cierge.pinniped.dev_credentialissuers.yaml | 77 ++++++++++++- generated/1.17/README.adoc | 84 ++++++++++++++- .../config/v1alpha1/types_credentialissuer.go | 102 +++++++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 58 ++++++++++ ...cierge.pinniped.dev_credentialissuers.yaml | 77 ++++++++++++- generated/1.18/README.adoc | 84 ++++++++++++++- .../config/v1alpha1/types_credentialissuer.go | 102 +++++++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 58 ++++++++++ ...cierge.pinniped.dev_credentialissuers.yaml | 77 ++++++++++++- generated/1.19/README.adoc | 84 ++++++++++++++- .../config/v1alpha1/types_credentialissuer.go | 102 +++++++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 58 ++++++++++ ...cierge.pinniped.dev_credentialissuers.yaml | 77 ++++++++++++- generated/1.20/README.adoc | 84 ++++++++++++++- .../config/v1alpha1/types_credentialissuer.go | 102 +++++++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 58 ++++++++++ ...cierge.pinniped.dev_credentialissuers.yaml | 77 ++++++++++++- .../config/v1alpha1/types_credentialissuer.go | 102 +++++++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 58 ++++++++++ 19 files changed, 1502 insertions(+), 19 deletions(-) diff --git a/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml b/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml index 992331de..60ffaee3 100644 --- a/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml @@ -36,8 +36,83 @@ spec: type: string metadata: type: object + spec: + default: + impersonationProxy: + mode: disabled + service: + type: LoadBalancer + description: Spec describes the intended configuration of the Concierge. + properties: + impersonationProxy: + default: + mode: disabled + service: + type: LoadBalancer + description: ImpersonationProxy describes the intended configuration + of the Concierge impersonation proxy. + properties: + externalEndpoint: + description: "ExternalEndpoint describes the HTTPS endpoint where + the proxy will be exposed. If the proxy is enabled and this + field is not set, a Service of type LoadBalancer will be automatically + provisioned and its external name will be advertised. \n Setting + this field disables the automatic creation of this LoadBalancer + Service." + type: string + mode: + default: disabled + description: 'Mode configures whether the impersonation proxy + should be started: - "disabled" explicitly disables the impersonation + proxy. This is the default. - "enabled" explicitly enables the + impersonation proxy. - "auto" enables or disables the impersonation + proxy based upon the cluster in which it is running.' + enum: + - auto + - enabled + - disabled + type: string + service: + default: + type: LoadBalancer + description: Service describes the configuraiton + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies zero or more key/value + pairs to set as annotations on the provisioned Service. + type: object + loadBalancerIP: + description: LoadBalancerIP specifies the IP address to set + in the spec.loadBalancerIP field of the provisioned Service. + This is not supported on all cloud providers. + maxLength: 255 + minLength: 1 + type: string + type: + default: LoadBalancer + description: "Type specifies the type of Service to provision + for the impersonation proxy. \n If the type is \"None\", + then the \"spec.impersonationProxy.externalEndpoint\" field + must be set to a non-empty value so that the Concierge can + properly advertise the endpoint in the CredentialIssuer's + status." + enum: + - LoadBalancer + - ClusterIP + - None + type: string + type: object + required: + - mode + - service + type: object + required: + - impersonationProxy + type: object status: - description: Status of the credential issuer. + description: CredentialIssuerStatus describes the status of the Concierge. properties: kubeConfigInfo: description: Information needed to form a valid Pinniped-based kubeconfig diff --git a/generated/1.17/README.adoc b/generated/1.17/README.adoc index 67ca2720..955e4920 100644 --- a/generated/1.17/README.adoc +++ b/generated/1.17/README.adoc @@ -232,7 +232,8 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | Status of the credential issuer. +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$]__ | Spec describes the intended configuration of the Concierge. +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | CredentialIssuerStatus describes the status of the Concierge. |=== @@ -275,6 +276,23 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerspec"] +==== CredentialIssuerSpec + +CredentialIssuerSpec describes the intended configuration of the Concierge. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuer[$$CredentialIssuer$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`impersonationProxy`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$]__ | ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus @@ -333,6 +351,70 @@ CredentialIssuerStatus describes the status of the Concierge. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxymode"] +==== ImpersonationProxyMode (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec"] +==== ImpersonationProxyServiceSpec + +ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __ImpersonationProxyServiceType__ | Type specifies the type of Service to provision for the impersonation proxy. + If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. +| *`loadBalancerIP`* __string__ | LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. This is not supported on all cloud providers. +| *`annotations`* __object (keys:string, values:string)__ | Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicetype"] +==== ImpersonationProxyServiceType (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec"] +==== ImpersonationProxySpec + +ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. + Setting this field disables the automatic creation of this LoadBalancer Service. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"] ==== TokenCredentialRequestAPIInfo diff --git a/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go index 4a6607c5..c102c1a8 100644 --- a/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -3,7 +3,9 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy @@ -40,6 +42,95 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) +// CredentialIssuerSpec describes the intended configuration of the Concierge. +type CredentialIssuerSpec struct { + // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. + // + //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} + ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` +} + +// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. +// +// +kubebuilder:validation:Enum=auto;enabled;disabled +type ImpersonationProxyMode string + +const ( + // ImpersonationProxyModeDisabled explicitly disables the impersonation proxy. + ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled") + + // ImpersonationProxyModeEnabled explicitly enables the impersonation proxy. + ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled") + + // ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running. + ImpersonationProxyModeAuto = ImpersonationProxyMode("auto") +) + +// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy. +// +// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None +type ImpersonationProxyServiceType string + +const ( + // ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer. + ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer") + + // ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP. + ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP") + + // ImpersonationProxyServiceTypeNone does not automatically provision any service. + ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") +) + +// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. +type ImpersonationProxySpec struct { + // Mode configures whether the impersonation proxy should be started: + // - "disabled" explicitly disables the impersonation proxy. This is the default. + // - "enabled" explicitly enables the impersonation proxy. + // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. + // + // +kubebuilder:default:="disabled" + Mode ImpersonationProxyMode `json:"mode"` + + // Service describes the configuraiton + // + // +kubebuilder:default:={"type": "LoadBalancer"} + Service ImpersonationProxyServiceSpec `json:"service"` + + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this + // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be + // advertised. + // + // Setting this field disables the automatic creation of this LoadBalancer Service. + // + // +optional + ExternalEndpoint string `json:"externalEndpoint,omitempty"` +} + +// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. +type ImpersonationProxyServiceSpec struct { + // Type specifies the type of Service to provision for the impersonation proxy. + // + // If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty + // value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. + // + // +kubebuilder:default:="LoadBalancer" + Type ImpersonationProxyServiceType `json:"type,omitempty"` + + // LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. + // This is not supported on all cloud providers. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +optional + LoadBalancerIP string `json:"loadBalancerIP,omitempty"` + + // Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. + // + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. @@ -134,7 +225,14 @@ type CredentialIssuer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Status of the credential issuer. + // Spec describes the intended configuration of the Concierge. + // + // +optional + // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} + Spec CredentialIssuerSpec `json:"spec"` + + // CredentialIssuerStatus describes the status of the Concierge. + // // +optional Status CredentialIssuerStatus `json:"status"` } diff --git a/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index 4a3ad605..d5b38b36 100644 --- a/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -16,6 +16,7 @@ func (in *CredentialIssuer) DeepCopyInto(out *CredentialIssuer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } @@ -113,6 +114,23 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { + *out = *in + in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialIssuerSpec. +func (in *CredentialIssuerSpec) DeepCopy() *CredentialIssuerSpec { + if in == nil { + return nil + } + out := new(CredentialIssuerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerStatus) DeepCopyInto(out *CredentialIssuerStatus) { *out = *in @@ -179,6 +197,46 @@ func (in *ImpersonationProxyInfo) DeepCopy() *ImpersonationProxyInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxyServiceSpec) DeepCopyInto(out *ImpersonationProxyServiceSpec) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyServiceSpec. +func (in *ImpersonationProxyServiceSpec) DeepCopy() *ImpersonationProxyServiceSpec { + if in == nil { + return nil + } + out := new(ImpersonationProxyServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) { + *out = *in + in.Service.DeepCopyInto(&out.Service) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxySpec. +func (in *ImpersonationProxySpec) DeepCopy() *ImpersonationProxySpec { + if in == nil { + return nil + } + out := new(ImpersonationProxySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) { *out = *in diff --git a/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 992331de..60ffaee3 100644 --- a/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -36,8 +36,83 @@ spec: type: string metadata: type: object + spec: + default: + impersonationProxy: + mode: disabled + service: + type: LoadBalancer + description: Spec describes the intended configuration of the Concierge. + properties: + impersonationProxy: + default: + mode: disabled + service: + type: LoadBalancer + description: ImpersonationProxy describes the intended configuration + of the Concierge impersonation proxy. + properties: + externalEndpoint: + description: "ExternalEndpoint describes the HTTPS endpoint where + the proxy will be exposed. If the proxy is enabled and this + field is not set, a Service of type LoadBalancer will be automatically + provisioned and its external name will be advertised. \n Setting + this field disables the automatic creation of this LoadBalancer + Service." + type: string + mode: + default: disabled + description: 'Mode configures whether the impersonation proxy + should be started: - "disabled" explicitly disables the impersonation + proxy. This is the default. - "enabled" explicitly enables the + impersonation proxy. - "auto" enables or disables the impersonation + proxy based upon the cluster in which it is running.' + enum: + - auto + - enabled + - disabled + type: string + service: + default: + type: LoadBalancer + description: Service describes the configuraiton + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies zero or more key/value + pairs to set as annotations on the provisioned Service. + type: object + loadBalancerIP: + description: LoadBalancerIP specifies the IP address to set + in the spec.loadBalancerIP field of the provisioned Service. + This is not supported on all cloud providers. + maxLength: 255 + minLength: 1 + type: string + type: + default: LoadBalancer + description: "Type specifies the type of Service to provision + for the impersonation proxy. \n If the type is \"None\", + then the \"spec.impersonationProxy.externalEndpoint\" field + must be set to a non-empty value so that the Concierge can + properly advertise the endpoint in the CredentialIssuer's + status." + enum: + - LoadBalancer + - ClusterIP + - None + type: string + type: object + required: + - mode + - service + type: object + required: + - impersonationProxy + type: object status: - description: Status of the credential issuer. + description: CredentialIssuerStatus describes the status of the Concierge. properties: kubeConfigInfo: description: Information needed to form a valid Pinniped-based kubeconfig diff --git a/generated/1.18/README.adoc b/generated/1.18/README.adoc index 5f9555d9..6163e1ed 100644 --- a/generated/1.18/README.adoc +++ b/generated/1.18/README.adoc @@ -232,7 +232,8 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | Status of the credential issuer. +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$]__ | Spec describes the intended configuration of the Concierge. +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | CredentialIssuerStatus describes the status of the Concierge. |=== @@ -275,6 +276,23 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuerspec"] +==== CredentialIssuerSpec + +CredentialIssuerSpec describes the intended configuration of the Concierge. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuer[$$CredentialIssuer$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`impersonationProxy`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$]__ | ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus @@ -333,6 +351,70 @@ CredentialIssuerStatus describes the status of the Concierge. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxymode"] +==== ImpersonationProxyMode (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyservicespec"] +==== ImpersonationProxyServiceSpec + +ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __ImpersonationProxyServiceType__ | Type specifies the type of Service to provision for the impersonation proxy. + If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. +| *`loadBalancerIP`* __string__ | LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. This is not supported on all cloud providers. +| *`annotations`* __object (keys:string, values:string)__ | Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyservicetype"] +==== ImpersonationProxyServiceType (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyspec"] +==== ImpersonationProxySpec + +ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. + Setting this field disables the automatic creation of this LoadBalancer Service. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"] ==== TokenCredentialRequestAPIInfo diff --git a/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go index 4a6607c5..c102c1a8 100644 --- a/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -3,7 +3,9 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy @@ -40,6 +42,95 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) +// CredentialIssuerSpec describes the intended configuration of the Concierge. +type CredentialIssuerSpec struct { + // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. + // + //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} + ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` +} + +// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. +// +// +kubebuilder:validation:Enum=auto;enabled;disabled +type ImpersonationProxyMode string + +const ( + // ImpersonationProxyModeDisabled explicitly disables the impersonation proxy. + ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled") + + // ImpersonationProxyModeEnabled explicitly enables the impersonation proxy. + ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled") + + // ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running. + ImpersonationProxyModeAuto = ImpersonationProxyMode("auto") +) + +// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy. +// +// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None +type ImpersonationProxyServiceType string + +const ( + // ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer. + ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer") + + // ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP. + ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP") + + // ImpersonationProxyServiceTypeNone does not automatically provision any service. + ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") +) + +// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. +type ImpersonationProxySpec struct { + // Mode configures whether the impersonation proxy should be started: + // - "disabled" explicitly disables the impersonation proxy. This is the default. + // - "enabled" explicitly enables the impersonation proxy. + // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. + // + // +kubebuilder:default:="disabled" + Mode ImpersonationProxyMode `json:"mode"` + + // Service describes the configuraiton + // + // +kubebuilder:default:={"type": "LoadBalancer"} + Service ImpersonationProxyServiceSpec `json:"service"` + + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this + // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be + // advertised. + // + // Setting this field disables the automatic creation of this LoadBalancer Service. + // + // +optional + ExternalEndpoint string `json:"externalEndpoint,omitempty"` +} + +// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. +type ImpersonationProxyServiceSpec struct { + // Type specifies the type of Service to provision for the impersonation proxy. + // + // If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty + // value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. + // + // +kubebuilder:default:="LoadBalancer" + Type ImpersonationProxyServiceType `json:"type,omitempty"` + + // LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. + // This is not supported on all cloud providers. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +optional + LoadBalancerIP string `json:"loadBalancerIP,omitempty"` + + // Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. + // + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. @@ -134,7 +225,14 @@ type CredentialIssuer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Status of the credential issuer. + // Spec describes the intended configuration of the Concierge. + // + // +optional + // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} + Spec CredentialIssuerSpec `json:"spec"` + + // CredentialIssuerStatus describes the status of the Concierge. + // // +optional Status CredentialIssuerStatus `json:"status"` } diff --git a/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index 4a3ad605..d5b38b36 100644 --- a/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -16,6 +16,7 @@ func (in *CredentialIssuer) DeepCopyInto(out *CredentialIssuer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } @@ -113,6 +114,23 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { + *out = *in + in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialIssuerSpec. +func (in *CredentialIssuerSpec) DeepCopy() *CredentialIssuerSpec { + if in == nil { + return nil + } + out := new(CredentialIssuerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerStatus) DeepCopyInto(out *CredentialIssuerStatus) { *out = *in @@ -179,6 +197,46 @@ func (in *ImpersonationProxyInfo) DeepCopy() *ImpersonationProxyInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxyServiceSpec) DeepCopyInto(out *ImpersonationProxyServiceSpec) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyServiceSpec. +func (in *ImpersonationProxyServiceSpec) DeepCopy() *ImpersonationProxyServiceSpec { + if in == nil { + return nil + } + out := new(ImpersonationProxyServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) { + *out = *in + in.Service.DeepCopyInto(&out.Service) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxySpec. +func (in *ImpersonationProxySpec) DeepCopy() *ImpersonationProxySpec { + if in == nil { + return nil + } + out := new(ImpersonationProxySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) { *out = *in diff --git a/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 992331de..60ffaee3 100644 --- a/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -36,8 +36,83 @@ spec: type: string metadata: type: object + spec: + default: + impersonationProxy: + mode: disabled + service: + type: LoadBalancer + description: Spec describes the intended configuration of the Concierge. + properties: + impersonationProxy: + default: + mode: disabled + service: + type: LoadBalancer + description: ImpersonationProxy describes the intended configuration + of the Concierge impersonation proxy. + properties: + externalEndpoint: + description: "ExternalEndpoint describes the HTTPS endpoint where + the proxy will be exposed. If the proxy is enabled and this + field is not set, a Service of type LoadBalancer will be automatically + provisioned and its external name will be advertised. \n Setting + this field disables the automatic creation of this LoadBalancer + Service." + type: string + mode: + default: disabled + description: 'Mode configures whether the impersonation proxy + should be started: - "disabled" explicitly disables the impersonation + proxy. This is the default. - "enabled" explicitly enables the + impersonation proxy. - "auto" enables or disables the impersonation + proxy based upon the cluster in which it is running.' + enum: + - auto + - enabled + - disabled + type: string + service: + default: + type: LoadBalancer + description: Service describes the configuraiton + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies zero or more key/value + pairs to set as annotations on the provisioned Service. + type: object + loadBalancerIP: + description: LoadBalancerIP specifies the IP address to set + in the spec.loadBalancerIP field of the provisioned Service. + This is not supported on all cloud providers. + maxLength: 255 + minLength: 1 + type: string + type: + default: LoadBalancer + description: "Type specifies the type of Service to provision + for the impersonation proxy. \n If the type is \"None\", + then the \"spec.impersonationProxy.externalEndpoint\" field + must be set to a non-empty value so that the Concierge can + properly advertise the endpoint in the CredentialIssuer's + status." + enum: + - LoadBalancer + - ClusterIP + - None + type: string + type: object + required: + - mode + - service + type: object + required: + - impersonationProxy + type: object status: - description: Status of the credential issuer. + description: CredentialIssuerStatus describes the status of the Concierge. properties: kubeConfigInfo: description: Information needed to form a valid Pinniped-based kubeconfig diff --git a/generated/1.19/README.adoc b/generated/1.19/README.adoc index 8f16ea55..db74e17f 100644 --- a/generated/1.19/README.adoc +++ b/generated/1.19/README.adoc @@ -232,7 +232,8 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | Status of the credential issuer. +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$]__ | Spec describes the intended configuration of the Concierge. +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | CredentialIssuerStatus describes the status of the Concierge. |=== @@ -275,6 +276,23 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuerspec"] +==== CredentialIssuerSpec + +CredentialIssuerSpec describes the intended configuration of the Concierge. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuer[$$CredentialIssuer$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`impersonationProxy`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$]__ | ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus @@ -333,6 +351,70 @@ CredentialIssuerStatus describes the status of the Concierge. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxymode"] +==== ImpersonationProxyMode (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyservicespec"] +==== ImpersonationProxyServiceSpec + +ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __ImpersonationProxyServiceType__ | Type specifies the type of Service to provision for the impersonation proxy. + If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. +| *`loadBalancerIP`* __string__ | LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. This is not supported on all cloud providers. +| *`annotations`* __object (keys:string, values:string)__ | Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyservicetype"] +==== ImpersonationProxyServiceType (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyspec"] +==== ImpersonationProxySpec + +ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. + Setting this field disables the automatic creation of this LoadBalancer Service. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"] ==== TokenCredentialRequestAPIInfo diff --git a/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go index 4a6607c5..c102c1a8 100644 --- a/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -3,7 +3,9 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy @@ -40,6 +42,95 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) +// CredentialIssuerSpec describes the intended configuration of the Concierge. +type CredentialIssuerSpec struct { + // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. + // + //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} + ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` +} + +// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. +// +// +kubebuilder:validation:Enum=auto;enabled;disabled +type ImpersonationProxyMode string + +const ( + // ImpersonationProxyModeDisabled explicitly disables the impersonation proxy. + ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled") + + // ImpersonationProxyModeEnabled explicitly enables the impersonation proxy. + ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled") + + // ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running. + ImpersonationProxyModeAuto = ImpersonationProxyMode("auto") +) + +// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy. +// +// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None +type ImpersonationProxyServiceType string + +const ( + // ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer. + ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer") + + // ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP. + ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP") + + // ImpersonationProxyServiceTypeNone does not automatically provision any service. + ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") +) + +// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. +type ImpersonationProxySpec struct { + // Mode configures whether the impersonation proxy should be started: + // - "disabled" explicitly disables the impersonation proxy. This is the default. + // - "enabled" explicitly enables the impersonation proxy. + // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. + // + // +kubebuilder:default:="disabled" + Mode ImpersonationProxyMode `json:"mode"` + + // Service describes the configuraiton + // + // +kubebuilder:default:={"type": "LoadBalancer"} + Service ImpersonationProxyServiceSpec `json:"service"` + + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this + // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be + // advertised. + // + // Setting this field disables the automatic creation of this LoadBalancer Service. + // + // +optional + ExternalEndpoint string `json:"externalEndpoint,omitempty"` +} + +// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. +type ImpersonationProxyServiceSpec struct { + // Type specifies the type of Service to provision for the impersonation proxy. + // + // If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty + // value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. + // + // +kubebuilder:default:="LoadBalancer" + Type ImpersonationProxyServiceType `json:"type,omitempty"` + + // LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. + // This is not supported on all cloud providers. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +optional + LoadBalancerIP string `json:"loadBalancerIP,omitempty"` + + // Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. + // + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. @@ -134,7 +225,14 @@ type CredentialIssuer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Status of the credential issuer. + // Spec describes the intended configuration of the Concierge. + // + // +optional + // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} + Spec CredentialIssuerSpec `json:"spec"` + + // CredentialIssuerStatus describes the status of the Concierge. + // // +optional Status CredentialIssuerStatus `json:"status"` } diff --git a/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index 4a3ad605..d5b38b36 100644 --- a/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -16,6 +16,7 @@ func (in *CredentialIssuer) DeepCopyInto(out *CredentialIssuer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } @@ -113,6 +114,23 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { + *out = *in + in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialIssuerSpec. +func (in *CredentialIssuerSpec) DeepCopy() *CredentialIssuerSpec { + if in == nil { + return nil + } + out := new(CredentialIssuerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerStatus) DeepCopyInto(out *CredentialIssuerStatus) { *out = *in @@ -179,6 +197,46 @@ func (in *ImpersonationProxyInfo) DeepCopy() *ImpersonationProxyInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxyServiceSpec) DeepCopyInto(out *ImpersonationProxyServiceSpec) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyServiceSpec. +func (in *ImpersonationProxyServiceSpec) DeepCopy() *ImpersonationProxyServiceSpec { + if in == nil { + return nil + } + out := new(ImpersonationProxyServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) { + *out = *in + in.Service.DeepCopyInto(&out.Service) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxySpec. +func (in *ImpersonationProxySpec) DeepCopy() *ImpersonationProxySpec { + if in == nil { + return nil + } + out := new(ImpersonationProxySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) { *out = *in diff --git a/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 992331de..60ffaee3 100644 --- a/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -36,8 +36,83 @@ spec: type: string metadata: type: object + spec: + default: + impersonationProxy: + mode: disabled + service: + type: LoadBalancer + description: Spec describes the intended configuration of the Concierge. + properties: + impersonationProxy: + default: + mode: disabled + service: + type: LoadBalancer + description: ImpersonationProxy describes the intended configuration + of the Concierge impersonation proxy. + properties: + externalEndpoint: + description: "ExternalEndpoint describes the HTTPS endpoint where + the proxy will be exposed. If the proxy is enabled and this + field is not set, a Service of type LoadBalancer will be automatically + provisioned and its external name will be advertised. \n Setting + this field disables the automatic creation of this LoadBalancer + Service." + type: string + mode: + default: disabled + description: 'Mode configures whether the impersonation proxy + should be started: - "disabled" explicitly disables the impersonation + proxy. This is the default. - "enabled" explicitly enables the + impersonation proxy. - "auto" enables or disables the impersonation + proxy based upon the cluster in which it is running.' + enum: + - auto + - enabled + - disabled + type: string + service: + default: + type: LoadBalancer + description: Service describes the configuraiton + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies zero or more key/value + pairs to set as annotations on the provisioned Service. + type: object + loadBalancerIP: + description: LoadBalancerIP specifies the IP address to set + in the spec.loadBalancerIP field of the provisioned Service. + This is not supported on all cloud providers. + maxLength: 255 + minLength: 1 + type: string + type: + default: LoadBalancer + description: "Type specifies the type of Service to provision + for the impersonation proxy. \n If the type is \"None\", + then the \"spec.impersonationProxy.externalEndpoint\" field + must be set to a non-empty value so that the Concierge can + properly advertise the endpoint in the CredentialIssuer's + status." + enum: + - LoadBalancer + - ClusterIP + - None + type: string + type: object + required: + - mode + - service + type: object + required: + - impersonationProxy + type: object status: - description: Status of the credential issuer. + description: CredentialIssuerStatus describes the status of the Concierge. properties: kubeConfigInfo: description: Information needed to form a valid Pinniped-based kubeconfig diff --git a/generated/1.20/README.adoc b/generated/1.20/README.adoc index 0bbef389..3a71b4ad 100644 --- a/generated/1.20/README.adoc +++ b/generated/1.20/README.adoc @@ -232,7 +232,8 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.2/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | Status of the credential issuer. +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$]__ | Spec describes the intended configuration of the Concierge. +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | CredentialIssuerStatus describes the status of the Concierge. |=== @@ -275,6 +276,23 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuerspec"] +==== CredentialIssuerSpec + +CredentialIssuerSpec describes the intended configuration of the Concierge. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuer[$$CredentialIssuer$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`impersonationProxy`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$]__ | ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuerstatus"] ==== CredentialIssuerStatus @@ -333,6 +351,70 @@ CredentialIssuerStatus describes the status of the Concierge. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxymode"] +==== ImpersonationProxyMode (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyservicespec"] +==== ImpersonationProxyServiceSpec + +ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __ImpersonationProxyServiceType__ | Type specifies the type of Service to provision for the impersonation proxy. + If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. +| *`loadBalancerIP`* __string__ | LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. This is not supported on all cloud providers. +| *`annotations`* __object (keys:string, values:string)__ | Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyservicetype"] +==== ImpersonationProxyServiceType (string) + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyspec"] +==== ImpersonationProxySpec + +ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. + Setting this field disables the automatic creation of this LoadBalancer Service. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"] ==== TokenCredentialRequestAPIInfo diff --git a/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go index 4a6607c5..c102c1a8 100644 --- a/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -3,7 +3,9 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy @@ -40,6 +42,95 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) +// CredentialIssuerSpec describes the intended configuration of the Concierge. +type CredentialIssuerSpec struct { + // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. + // + //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} + ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` +} + +// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. +// +// +kubebuilder:validation:Enum=auto;enabled;disabled +type ImpersonationProxyMode string + +const ( + // ImpersonationProxyModeDisabled explicitly disables the impersonation proxy. + ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled") + + // ImpersonationProxyModeEnabled explicitly enables the impersonation proxy. + ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled") + + // ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running. + ImpersonationProxyModeAuto = ImpersonationProxyMode("auto") +) + +// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy. +// +// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None +type ImpersonationProxyServiceType string + +const ( + // ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer. + ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer") + + // ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP. + ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP") + + // ImpersonationProxyServiceTypeNone does not automatically provision any service. + ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") +) + +// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. +type ImpersonationProxySpec struct { + // Mode configures whether the impersonation proxy should be started: + // - "disabled" explicitly disables the impersonation proxy. This is the default. + // - "enabled" explicitly enables the impersonation proxy. + // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. + // + // +kubebuilder:default:="disabled" + Mode ImpersonationProxyMode `json:"mode"` + + // Service describes the configuraiton + // + // +kubebuilder:default:={"type": "LoadBalancer"} + Service ImpersonationProxyServiceSpec `json:"service"` + + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this + // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be + // advertised. + // + // Setting this field disables the automatic creation of this LoadBalancer Service. + // + // +optional + ExternalEndpoint string `json:"externalEndpoint,omitempty"` +} + +// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. +type ImpersonationProxyServiceSpec struct { + // Type specifies the type of Service to provision for the impersonation proxy. + // + // If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty + // value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. + // + // +kubebuilder:default:="LoadBalancer" + Type ImpersonationProxyServiceType `json:"type,omitempty"` + + // LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. + // This is not supported on all cloud providers. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +optional + LoadBalancerIP string `json:"loadBalancerIP,omitempty"` + + // Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. + // + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. @@ -134,7 +225,14 @@ type CredentialIssuer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Status of the credential issuer. + // Spec describes the intended configuration of the Concierge. + // + // +optional + // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} + Spec CredentialIssuerSpec `json:"spec"` + + // CredentialIssuerStatus describes the status of the Concierge. + // // +optional Status CredentialIssuerStatus `json:"status"` } diff --git a/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index 4a3ad605..d5b38b36 100644 --- a/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -16,6 +16,7 @@ func (in *CredentialIssuer) DeepCopyInto(out *CredentialIssuer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } @@ -113,6 +114,23 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { + *out = *in + in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialIssuerSpec. +func (in *CredentialIssuerSpec) DeepCopy() *CredentialIssuerSpec { + if in == nil { + return nil + } + out := new(CredentialIssuerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerStatus) DeepCopyInto(out *CredentialIssuerStatus) { *out = *in @@ -179,6 +197,46 @@ func (in *ImpersonationProxyInfo) DeepCopy() *ImpersonationProxyInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxyServiceSpec) DeepCopyInto(out *ImpersonationProxyServiceSpec) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyServiceSpec. +func (in *ImpersonationProxyServiceSpec) DeepCopy() *ImpersonationProxyServiceSpec { + if in == nil { + return nil + } + out := new(ImpersonationProxyServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) { + *out = *in + in.Service.DeepCopyInto(&out.Service) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxySpec. +func (in *ImpersonationProxySpec) DeepCopy() *ImpersonationProxySpec { + if in == nil { + return nil + } + out := new(ImpersonationProxySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) { *out = *in diff --git a/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 992331de..60ffaee3 100644 --- a/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -36,8 +36,83 @@ spec: type: string metadata: type: object + spec: + default: + impersonationProxy: + mode: disabled + service: + type: LoadBalancer + description: Spec describes the intended configuration of the Concierge. + properties: + impersonationProxy: + default: + mode: disabled + service: + type: LoadBalancer + description: ImpersonationProxy describes the intended configuration + of the Concierge impersonation proxy. + properties: + externalEndpoint: + description: "ExternalEndpoint describes the HTTPS endpoint where + the proxy will be exposed. If the proxy is enabled and this + field is not set, a Service of type LoadBalancer will be automatically + provisioned and its external name will be advertised. \n Setting + this field disables the automatic creation of this LoadBalancer + Service." + type: string + mode: + default: disabled + description: 'Mode configures whether the impersonation proxy + should be started: - "disabled" explicitly disables the impersonation + proxy. This is the default. - "enabled" explicitly enables the + impersonation proxy. - "auto" enables or disables the impersonation + proxy based upon the cluster in which it is running.' + enum: + - auto + - enabled + - disabled + type: string + service: + default: + type: LoadBalancer + description: Service describes the configuraiton + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies zero or more key/value + pairs to set as annotations on the provisioned Service. + type: object + loadBalancerIP: + description: LoadBalancerIP specifies the IP address to set + in the spec.loadBalancerIP field of the provisioned Service. + This is not supported on all cloud providers. + maxLength: 255 + minLength: 1 + type: string + type: + default: LoadBalancer + description: "Type specifies the type of Service to provision + for the impersonation proxy. \n If the type is \"None\", + then the \"spec.impersonationProxy.externalEndpoint\" field + must be set to a non-empty value so that the Concierge can + properly advertise the endpoint in the CredentialIssuer's + status." + enum: + - LoadBalancer + - ClusterIP + - None + type: string + type: object + required: + - mode + - service + type: object + required: + - impersonationProxy + type: object status: - description: Status of the credential issuer. + description: CredentialIssuerStatus describes the status of the Concierge. properties: kubeConfigInfo: description: Information needed to form a valid Pinniped-based kubeconfig diff --git a/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go index 4a6607c5..c102c1a8 100644 --- a/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -3,7 +3,9 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // StrategyType enumerates a type of "strategy" used to implement credential access on a cluster. // +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy @@ -40,6 +42,95 @@ const ( FetchedKeyStrategyReason = StrategyReason("FetchedKey") ) +// CredentialIssuerSpec describes the intended configuration of the Concierge. +type CredentialIssuerSpec struct { + // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. + // + //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} + ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` +} + +// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. +// +// +kubebuilder:validation:Enum=auto;enabled;disabled +type ImpersonationProxyMode string + +const ( + // ImpersonationProxyModeDisabled explicitly disables the impersonation proxy. + ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled") + + // ImpersonationProxyModeEnabled explicitly enables the impersonation proxy. + ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled") + + // ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running. + ImpersonationProxyModeAuto = ImpersonationProxyMode("auto") +) + +// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy. +// +// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None +type ImpersonationProxyServiceType string + +const ( + // ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer. + ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer") + + // ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP. + ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP") + + // ImpersonationProxyServiceTypeNone does not automatically provision any service. + ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") +) + +// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. +type ImpersonationProxySpec struct { + // Mode configures whether the impersonation proxy should be started: + // - "disabled" explicitly disables the impersonation proxy. This is the default. + // - "enabled" explicitly enables the impersonation proxy. + // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. + // + // +kubebuilder:default:="disabled" + Mode ImpersonationProxyMode `json:"mode"` + + // Service describes the configuraiton + // + // +kubebuilder:default:={"type": "LoadBalancer"} + Service ImpersonationProxyServiceSpec `json:"service"` + + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this + // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be + // advertised. + // + // Setting this field disables the automatic creation of this LoadBalancer Service. + // + // +optional + ExternalEndpoint string `json:"externalEndpoint,omitempty"` +} + +// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. +type ImpersonationProxyServiceSpec struct { + // Type specifies the type of Service to provision for the impersonation proxy. + // + // If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty + // value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status. + // + // +kubebuilder:default:="LoadBalancer" + Type ImpersonationProxyServiceType `json:"type,omitempty"` + + // LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. + // This is not supported on all cloud providers. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +optional + LoadBalancerIP string `json:"loadBalancerIP,omitempty"` + + // Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service. + // + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // CredentialIssuerStatus describes the status of the Concierge. type CredentialIssuerStatus struct { // List of integration strategies that were attempted by Pinniped. @@ -134,7 +225,14 @@ type CredentialIssuer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Status of the credential issuer. + // Spec describes the intended configuration of the Concierge. + // + // +optional + // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} + Spec CredentialIssuerSpec `json:"spec"` + + // CredentialIssuerStatus describes the status of the Concierge. + // // +optional Status CredentialIssuerStatus `json:"status"` } diff --git a/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index 4a3ad605..d5b38b36 100644 --- a/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -16,6 +16,7 @@ func (in *CredentialIssuer) DeepCopyInto(out *CredentialIssuer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } @@ -113,6 +114,23 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { + *out = *in + in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialIssuerSpec. +func (in *CredentialIssuerSpec) DeepCopy() *CredentialIssuerSpec { + if in == nil { + return nil + } + out := new(CredentialIssuerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerStatus) DeepCopyInto(out *CredentialIssuerStatus) { *out = *in @@ -179,6 +197,46 @@ func (in *ImpersonationProxyInfo) DeepCopy() *ImpersonationProxyInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxyServiceSpec) DeepCopyInto(out *ImpersonationProxyServiceSpec) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyServiceSpec. +func (in *ImpersonationProxyServiceSpec) DeepCopy() *ImpersonationProxyServiceSpec { + if in == nil { + return nil + } + out := new(ImpersonationProxyServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) { + *out = *in + in.Service.DeepCopyInto(&out.Service) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxySpec. +func (in *ImpersonationProxySpec) DeepCopy() *ImpersonationProxySpec { + if in == nil { + return nil + } + out := new(ImpersonationProxySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) { *out = *in From 1a131e64fef6cd6367b1caf829fae6a08119a95b Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Mon, 17 May 2021 10:05:42 -0500 Subject: [PATCH 04/43] Start deploying an initial CredentialIssuer in our install YAML. Signed-off-by: Matt Moyer --- deploy/concierge/deployment.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/deploy/concierge/deployment.yaml b/deploy/concierge/deployment.yaml index 525257a7..47b9327c 100644 --- a/deploy/concierge/deployment.yaml +++ b/deploy/concierge/deployment.yaml @@ -247,3 +247,15 @@ spec: name: #@ defaultResourceNameWithSuffix("api") namespace: #@ namespace() port: 443 +--- +apiVersion: config.concierge.pinniped.dev/v1alpha1 +kind: CredentialIssuer +metadata: + name: #@ defaultResourceNameWithSuffix("config") +spec: + impersonationProxy: + mode: auto + service: + mode: LoadBalancer + annotations: + service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000" From 18ccf119051e5fa896f93b7c4d5bd0e3508210a4 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Mon, 17 May 2021 17:08:05 -0500 Subject: [PATCH 05/43] Update impersonatorconfig controller to use CredentialIssuer API instead of ConfigMap. Signed-off-by: Margo Crawford Signed-off-by: Matt Moyer --- internal/concierge/impersonator/config.go | 18 + internal/config/concierge/types.go | 8 +- .../impersonatorconfig/impersonator_config.go | 58 ++- .../impersonator_config_test.go | 359 +++++++++++------- .../controllermanager/prepare_controllers.go | 3 +- 5 files changed, 279 insertions(+), 167 deletions(-) diff --git a/internal/concierge/impersonator/config.go b/internal/concierge/impersonator/config.go index 60ab59bd..04137835 100644 --- a/internal/concierge/impersonator/config.go +++ b/internal/concierge/impersonator/config.go @@ -8,6 +8,8 @@ import ( v1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" + + "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" ) type Mode string @@ -63,3 +65,19 @@ func ConfigFromConfigMap(configMap *v1.ConfigMap) (*Config, error) { } return config, nil } + +func ConfigFromCredentialIssuer(credIssuer *v1alpha1.CredentialIssuer) (*Config, error) { + config := NewConfig() + switch mode := credIssuer.Spec.ImpersonationProxy.Mode; mode { + case v1alpha1.ImpersonationProxyModeAuto: + config.Mode = ModeAuto + case v1alpha1.ImpersonationProxyModeDisabled: + config.Mode = ModeDisabled + case v1alpha1.ImpersonationProxyModeEnabled: + config.Mode = ModeEnabled + default: + return nil, fmt.Errorf("invalid impersonation proxy mode %q, valid values are auto, disabled, or enabled", mode) + } + config.Endpoint = credIssuer.Spec.ImpersonationProxy.ExternalEndpoint + return config, nil +} diff --git a/internal/config/concierge/types.go b/internal/config/concierge/types.go index ecd36d0a..4b4e56a2 100644 --- a/internal/config/concierge/types.go +++ b/internal/config/concierge/types.go @@ -33,9 +33,11 @@ type APIConfigSpec struct { // NamesConfigSpec configures the names of some Kubernetes resources for the Concierge. type NamesConfigSpec struct { - ServingCertificateSecret string `json:"servingCertificateSecret"` - CredentialIssuer string `json:"credentialIssuer"` - APIService string `json:"apiService"` + ServingCertificateSecret string `json:"servingCertificateSecret"` + CredentialIssuer string `json:"credentialIssuer"` + APIService string `json:"apiService"` + + // TODO: remove this key entirely ImpersonationConfigMap string `json:"impersonationConfigMap"` ImpersonationLoadBalancerService string `json:"impersonationLoadBalancerService"` ImpersonationTLSCertificateSecret string `json:"impersonationTLSCertificateSecret"` diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index c94aae15..5db9b4ba 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -27,6 +27,7 @@ import ( "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" pinnipedclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned" + conciergeconfiginformers "go.pinniped.dev/generated/latest/client/concierge/informers/externalversions/config/v1alpha1" "go.pinniped.dev/internal/certauthority" "go.pinniped.dev/internal/clusterhost" "go.pinniped.dev/internal/concierge/impersonator" @@ -51,7 +52,6 @@ const ( type impersonatorConfigController struct { namespace string - configMapResourceName string credentialIssuerResourceName string generatedLoadBalancerServiceName string tlsSecretName string @@ -61,7 +61,7 @@ type impersonatorConfigController struct { k8sClient kubernetes.Interface pinnipedAPIClient pinnipedclientset.Interface - configMapsInformer corev1informers.ConfigMapInformer + credIssuerInformer conciergeconfiginformers.CredentialIssuerInformer servicesInformer corev1informers.ServiceInformer secretsInformer corev1informers.SecretInformer @@ -78,11 +78,10 @@ type impersonatorConfigController struct { func NewImpersonatorConfigController( namespace string, - configMapResourceName string, credentialIssuerResourceName string, k8sClient kubernetes.Interface, pinnipedAPIClient pinnipedclientset.Interface, - configMapsInformer corev1informers.ConfigMapInformer, + credentialIssuerInformer conciergeconfiginformers.CredentialIssuerInformer, servicesInformer corev1informers.ServiceInformer, secretsInformer corev1informers.SecretInformer, withInformer pinnipedcontroller.WithInformerOptionFunc, @@ -102,7 +101,6 @@ func NewImpersonatorConfigController( Name: "impersonator-config-controller", Syncer: &impersonatorConfigController{ namespace: namespace, - configMapResourceName: configMapResourceName, credentialIssuerResourceName: credentialIssuerResourceName, generatedLoadBalancerServiceName: generatedLoadBalancerServiceName, tlsSecretName: tlsSecretName, @@ -110,7 +108,7 @@ func NewImpersonatorConfigController( impersonationSignerSecretName: impersonationSignerSecretName, k8sClient: k8sClient, pinnipedAPIClient: pinnipedAPIClient, - configMapsInformer: configMapsInformer, + credIssuerInformer: credentialIssuerInformer, servicesInformer: servicesInformer, secretsInformer: secretsInformer, labels: labels, @@ -120,9 +118,10 @@ func NewImpersonatorConfigController( tlsServingCertDynamicCertProvider: dynamiccert.NewServingCert("impersonation-proxy-serving-cert"), }, }, - withInformer( - configMapsInformer, - pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(configMapResourceName, namespace), + withInformer(credentialIssuerInformer, + pinnipedcontroller.SimpleFilterWithSingletonQueue(func(obj metav1.Object) bool { + return obj.GetName() == credentialIssuerResourceName + }), controllerlib.InformerOption{}, ), withInformer( @@ -137,12 +136,9 @@ func NewImpersonatorConfigController( }, nil), controllerlib.InformerOption{}, ), - // Be sure to run once even if the ConfigMap that the informer is watching doesn't exist so we can implement + // Be sure to run once even if the CredentialIssuer that the informer is watching doesn't exist so we can implement // the default configuration behavior. - withInitialEvent(controllerlib.Key{ - Namespace: namespace, - Name: configMapResourceName, - }), + withInitialEvent(controllerlib.Key{Name: credentialIssuerResourceName}), // TODO fix these controller options to make this a singleton queue ) } @@ -264,30 +260,26 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v } func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*impersonator.Config, error) { - configMap, err := c.configMapsInformer.Lister().ConfigMaps(c.namespace).Get(c.configMapResourceName) - notFound := k8serrors.IsNotFound(err) - if err != nil && !notFound { - return nil, fmt.Errorf("failed to get %s/%s configmap: %w", c.namespace, c.configMapResourceName, err) - } + credIssuer, err := c.credIssuerInformer.Lister().Get(c.credentialIssuerResourceName) - var config *impersonator.Config - if notFound { + if k8serrors.IsNotFound(err) { plog.Info("Did not find impersonation proxy config: using default config values", - "configmap", c.configMapResourceName, - "namespace", c.namespace, - ) - config = impersonator.NewConfig() // use default configuration options - } else { - config, err = impersonator.ConfigFromConfigMap(configMap) - if err != nil { - return nil, fmt.Errorf("invalid impersonator configuration: %v", err) - } - plog.Info("Read impersonation proxy config", - "configmap", c.configMapResourceName, - "namespace", c.namespace, + "credentialIssuer", c.credentialIssuerResourceName, ) + return impersonator.NewConfig(), nil } + if err != nil { + return nil, fmt.Errorf("failed to get %s CredentialIssuer: %w", c.credentialIssuerResourceName, err) + } + + config, err := impersonator.ConfigFromCredentialIssuer(credIssuer) + if err != nil { + return nil, fmt.Errorf("invalid impersonator configuration: %v", err) + } + plog.Info("Read impersonation proxy config", + "credentialIssuer", c.credentialIssuerResourceName, + ) return config, nil } diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index c7f7767a..7fa569a8 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -35,6 +35,7 @@ import ( "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" pinnipedfake "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake" + pinnipedinformers "go.pinniped.dev/generated/latest/client/concierge/informers/externalversions" "go.pinniped.dev/internal/certauthority" "go.pinniped.dev/internal/controller/apicerts" "go.pinniped.dev/internal/controllerlib" @@ -46,7 +47,7 @@ import ( func TestImpersonatorConfigControllerOptions(t *testing.T) { spec.Run(t, "options", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" - const configMapResourceName = "some-configmap-resource-name" + const credentialIssuerResourceName = "some-credential-issuer-resource-name" const generatedLoadBalancerServiceName = "some-service-resource-name" const tlsSecretName = "some-tls-secret-name" //nolint:gosec // this is not a credential const caSecretName = "some-ca-secret-name" @@ -55,7 +56,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { var r *require.Assertions var observableWithInformerOption *testutil.ObservableWithInformerOption var observableWithInitialEventOption *testutil.ObservableWithInitialEventOption - var configMapsInformerFilter controllerlib.Filter + var credIssuerInformerFilter controllerlib.Filter var servicesInformerFilter controllerlib.Filter var secretsInformerFilter controllerlib.Filter @@ -63,18 +64,18 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { r = require.New(t) observableWithInformerOption = testutil.NewObservableWithInformerOption() observableWithInitialEventOption = testutil.NewObservableWithInitialEventOption() + pinnipedInformerFactory := pinnipedinformers.NewSharedInformerFactory(nil, 0) sharedInformerFactory := kubeinformers.NewSharedInformerFactory(nil, 0) - configMapsInformer := sharedInformerFactory.Core().V1().ConfigMaps() + credIssuerInformer := pinnipedInformerFactory.Config().V1alpha1().CredentialIssuers() servicesInformer := sharedInformerFactory.Core().V1().Services() secretsInformer := sharedInformerFactory.Core().V1().Secrets() _ = NewImpersonatorConfigController( installedInNamespace, - configMapResourceName, - "", + credentialIssuerResourceName, nil, nil, - configMapsInformer, + credIssuerInformer, servicesInformer, secretsInformer, observableWithInformerOption.WithInformer, @@ -88,57 +89,39 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { caSignerName, nil, ) - configMapsInformerFilter = observableWithInformerOption.GetFilterForInformer(configMapsInformer) + credIssuerInformerFilter = observableWithInformerOption.GetFilterForInformer(credIssuerInformer) servicesInformerFilter = observableWithInformerOption.GetFilterForInformer(servicesInformer) secretsInformerFilter = observableWithInformerOption.GetFilterForInformer(secretsInformer) }) - when("watching ConfigMap objects", func() { + when("watching CredentialIssuer objects", func() { var subject controllerlib.Filter - var target, wrongNamespace, wrongName, unrelated *corev1.ConfigMap + var target, wrongName, otherWrongName *v1alpha1.CredentialIssuer it.Before(func() { - subject = configMapsInformerFilter - target = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: configMapResourceName, Namespace: installedInNamespace}} - wrongNamespace = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: configMapResourceName, Namespace: "wrong-namespace"}} - wrongName = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace}} - unrelated = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}} + subject = credIssuerInformerFilter + target = &v1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}} + wrongName = &v1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name"}} + otherWrongName = &v1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "other-wrong-name"}} }) - when("the target ConfigMap changes", func() { + when("the target CredentialIssuer changes", func() { it("returns true to trigger the sync method", func() { r.True(subject.Add(target)) - r.True(subject.Update(target, unrelated)) - r.True(subject.Update(unrelated, target)) + r.True(subject.Update(target, wrongName)) + r.True(subject.Update(wrongName, target)) r.True(subject.Delete(target)) }) }) - when("a ConfigMap from another namespace changes", func() { - it("returns false to avoid triggering the sync method", func() { - r.False(subject.Add(wrongNamespace)) - r.False(subject.Update(wrongNamespace, unrelated)) - r.False(subject.Update(unrelated, wrongNamespace)) - r.False(subject.Delete(wrongNamespace)) - }) - }) - - when("a ConfigMap with a different name changes", func() { + when("a CredentialIssuer with a different name changes", func() { it("returns false to avoid triggering the sync method", func() { r.False(subject.Add(wrongName)) - r.False(subject.Update(wrongName, unrelated)) - r.False(subject.Update(unrelated, wrongName)) + r.False(subject.Update(wrongName, otherWrongName)) + r.False(subject.Update(otherWrongName, wrongName)) r.False(subject.Delete(wrongName)) }) }) - - when("a ConfigMap with a different name and a different namespace changes", func() { - it("returns false to avoid triggering the sync method", func() { - r.False(subject.Add(unrelated)) - r.False(subject.Update(unrelated, unrelated)) - r.False(subject.Delete(unrelated)) - }) - }) }) when("watching Service objects", func() { @@ -253,10 +236,9 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { }) when("starting up", func() { - it("asks for an initial event because the ConfigMap may not exist yet and it needs to run anyway", func() { + it("asks for an initial event because the CredentialIssuer may not exist yet and it needs to run anyway", func() { r.Equal(&controllerlib.Key{ - Namespace: installedInNamespace, - Name: configMapResourceName, + Name: credentialIssuerResourceName, }, observableWithInitialEventOption.GetInitialEventKey()) }) }) @@ -267,7 +249,6 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { name := t.Name() spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" - const configMapResourceName = "some-configmap-resource-name" const credentialIssuerResourceName = "some-credential-issuer-resource-name" const loadBalancerServiceName = "some-service-resource-name" const tlsSecretName = "some-tls-secret-name" //nolint:gosec // this is not a credential @@ -283,6 +264,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var subject controllerlib.Controller var kubeAPIClient *kubernetesfake.Clientset var pinnipedAPIClient *pinnipedfake.Clientset + var pinnipedInformerClient *pinnipedfake.Clientset + var pinnipedInformers pinnipedinformers.SharedInformerFactory var kubeInformerClient *kubernetesfake.Clientset var kubeInformers kubeinformers.SharedInformerFactory var cancelContext context.Context @@ -533,11 +516,10 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Set this at the last second to allow for injection of server override. subject = NewImpersonatorConfigController( installedInNamespace, - configMapResourceName, credentialIssuerResourceName, kubeAPIClient, pinnipedAPIClient, - kubeInformers.Core().V1().ConfigMaps(), + pinnipedInformers.Config().V1alpha1().CredentialIssuers(), kubeInformers.Core().V1().Services(), kubeInformers.Core().V1().Secrets(), controllerlib.WithInformer, @@ -557,28 +539,25 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { Context: cancelContext, Name: subject.Name(), Key: controllerlib.Key{ - Namespace: installedInNamespace, - Name: configMapResourceName, + Name: credentialIssuerResourceName, }, Queue: queue, } // Must start informers before calling TestRunSynchronously() kubeInformers.Start(cancelContext.Done()) + pinnipedInformers.Start(cancelContext.Done()) controllerlib.TestRunSynchronously(t, subject) } - var addImpersonatorConfigMapToTracker = func(resourceName, configYAML string, client *kubernetesfake.Clientset) { - impersonatorConfigMap := &corev1.ConfigMap{ + var addCredentialIssuerToTracker = func(resourceName string, credIssuerSpec v1alpha1.CredentialIssuerSpec, client *pinnipedfake.Clientset) { + t.Logf("adding CredentialIssuer %s to informer clientset", resourceName) + r.NoError(client.Tracker().Add(&v1alpha1.CredentialIssuer{ ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: installedInNamespace, + Name: resourceName, }, - Data: map[string]string{ - "config.yaml": configYAML, - }, - } - r.NoError(client.Tracker().Add(impersonatorConfigMap)) + Spec: credIssuerSpec, + })) } var newSecretWithData = func(resourceName string, data map[string][]byte) *corev1.Secret { @@ -670,6 +649,19 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal(obj, objFromInformer, "was waiting for expected to be found in informer, but found actual") } + var waitForClusterScopedObjectToAppearInInformer = func(obj kubeclient.Object, informer controllerlib.InformerGetter) { + var objFromInformer interface{} + var exists bool + var err error + assert.Eventually(t, func() bool { + objFromInformer, exists, err = informer.Informer().GetIndexer().GetByKey(obj.GetName()) + return err == nil && exists && reflect.DeepEqual(objFromInformer.(kubeclient.Object), obj) + }, 30*time.Second, 10*time.Millisecond) + r.NoError(err) + r.True(exists, "this object should have existed in informer but didn't: %+v", obj) + r.Equal(obj, objFromInformer, "was waiting for expected to be found in informer, but found actual") + } + // See comment for waitForObjectToAppearInInformer above. var waitForObjectToBeDeletedFromInformer = func(resourceName string, informer controllerlib.InformerGetter) { var objFromInformer interface{} @@ -683,7 +675,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.False(exists, "this object should have been deleted from informer but wasn't: %s", objFromInformer) } - var addObjectToInformerAndWait = func(obj kubeclient.Object, informer controllerlib.InformerGetter) { + var addObjectToKubeInformerAndWait = func(obj kubeclient.Object, informer controllerlib.InformerGetter) { r.NoError(kubeInformerClient.Tracker().Add(obj)) waitForObjectToAppearInInformer(obj, informer) } @@ -691,27 +683,19 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var addObjectFromCreateActionToInformerAndWait = func(action coretesting.Action, informer controllerlib.InformerGetter) { createdObject, ok := action.(coretesting.CreateAction).GetObject().(kubeclient.Object) r.True(ok, "should have been able to cast this action's object to kubeclient.Object: %v", action) - addObjectToInformerAndWait(createdObject, informer) + addObjectToKubeInformerAndWait(createdObject, informer) } - var updateImpersonatorConfigMapInInformerAndWait = func(resourceName, configYAML string, informer controllerlib.InformerGetter) { - configMapObj, err := kubeInformerClient.Tracker().Get( - schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, - installedInNamespace, - resourceName, - ) - r.NoError(err) - configMap := configMapObj.(*corev1.ConfigMap) - configMap = configMap.DeepCopy() // don't edit the original from the tracker - configMap.Data = map[string]string{ - "config.yaml": configYAML, - } - r.NoError(kubeInformerClient.Tracker().Update( - schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, - configMap, - installedInNamespace, - )) - waitForObjectToAppearInInformer(configMap, informer) + var updateCredentialIssuerInInformerAndWait = func(resourceName string, credIssuerSpec v1alpha1.CredentialIssuerSpec, informer controllerlib.InformerGetter) { + credIssuersGVR := v1alpha1.Resource("credentialissuers").WithVersion("v1alpha1") + credIssuerObj, err := pinnipedInformerClient.Tracker().Get(credIssuersGVR, "", resourceName) + r.NoError(err, "could not find CredentialIssuer to update for test") + + credIssuer := credIssuerObj.(*v1alpha1.CredentialIssuer) + credIssuer = credIssuer.DeepCopy() // don't edit the original from the tracker + credIssuer.Spec = credIssuerSpec + r.NoError(pinnipedInformerClient.Tracker().Update(credIssuersGVR, credIssuer, "")) + waitForClusterScopedObjectToAppearInInformer(credIssuer, informer) } var updateLoadBalancerServiceInInformerAndWait = func(resourceName string, ingresses []corev1.LoadBalancerIngress, informer controllerlib.InformerGetter) { @@ -968,6 +952,10 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r = require.New(t) queue = &testQueue{} cancelContext, cancelContextCancelFunc = context.WithCancel(context.Background()) + + pinnipedInformerClient = pinnipedfake.NewSimpleClientset() + pinnipedInformers = pinnipedinformers.NewSharedInformerFactoryWithOptions(pinnipedInformerClient, 0) + kubeInformerClient = kubernetesfake.NewSimpleClientset() kubeInformers = kubeinformers.NewSharedInformerFactoryWithOptions(kubeInformerClient, 0, kubeinformers.WithNamespace(installedInNamespace), @@ -992,10 +980,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { closeTestHTTPServer() }) - when("the ConfigMap does not yet exist in the installation namespace or it was deleted (defaults to auto mode)", func() { + when("the CredentialIssuer does not yet exist or it was deleted (defaults to auto mode)", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addImpersonatorConfigMapToTracker("some-other-unrelated-configmap", "foo: bar", kubeInformerClient) }) when("there are visible control plane nodes", func() { @@ -1286,15 +1273,19 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the ConfigMap is already in the installation namespace", func() { + when("the CredentialIssuer is already present", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) }) when("the configuration is auto mode with an endpoint", func() { it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: auto, endpoint: %s}", localhostIP) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + ExternalEndpoint: localhostIP, + }, + }, pinnipedInformerClient) }) when("there are visible control plane nodes", func() { @@ -1318,7 +1309,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addNodeWithRoleToTracker("worker", kubeAPIClient) }) - it("starts the impersonator according to the settings in the ConfigMap", func() { + it("starts the impersonator according to the settings in the CredentialIssuer", func() { startInformersAndController() r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 3) @@ -1334,7 +1325,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the configuration is disabled mode", func() { it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "mode: disabled", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1352,7 +1347,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the configuration is enabled mode", func() { when("no load balancer", func() { it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) }) @@ -1379,7 +1378,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("a loadbalancer already exists", func() { it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) @@ -1408,7 +1411,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("a load balancer and a secret already exists", func() { var caCrt []byte it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) ca := newCA() caSecret := newActualCASecret(ca, caSecretName) @@ -1431,11 +1438,15 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the configmap has a hostname specified for the endpoint", func() { + when("the CredentialIssuer has a hostname specified for the endpoint", func() { const fakeHostname = "fake.example.com" it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1453,11 +1464,15 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the configmap has a endpoint which is an IP address with a port", func() { + when("the CredentialIssuer has a endpoint which is an IP address with a port", func() { const fakeIPWithPort = "127.0.0.1:3000" it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeIPWithPort) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeIPWithPort, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1475,11 +1490,15 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the configmap has a endpoint which is a hostname with a port", func() { + when("the CredentialIssuer has a endpoint which is a hostname with a port", func() { const fakeHostnameWithPort = "fake.example.com:3000" it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostnameWithPort) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostnameWithPort, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1497,13 +1516,25 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("switching the configmap from ip address endpoint to hostname endpoint and back to ip address", func() { + when("switching the CredentialIssuer from ip address endpoint to hostname endpoint and back to ip address", func() { const fakeHostname = "fake.example.com" const fakeIP = "127.0.0.42" - var hostnameYAML = fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname) - var ipAddressYAML = fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeIP) + + var hostnameConfig = v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + }, + } + var ipAddressConfig = v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeIP, + }, + } + it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, ipAddressYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, ipAddressConfig, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1524,7 +1555,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) // Switch the endpoint config to a hostname. - updateImpersonatorConfigMapInInformerAndWait(configMapResourceName, hostnameYAML, kubeInformers.Core().V1().ConfigMaps()) + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, hostnameConfig, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 5) @@ -1541,7 +1572,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[4], kubeInformers.Core().V1().Secrets()) // Switch the endpoint config back to an IP. - updateImpersonatorConfigMapInInformerAndWait(configMapResourceName, ipAddressYAML, kubeInformers.Core().V1().ConfigMaps()) + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, ipAddressConfig, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 7) @@ -1557,8 +1588,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the TLS cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { const fakeHostname = "fake.example.com" it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) startInformersAndController() }) @@ -1595,8 +1630,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CA cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { const fakeHostname = "fake.example.com" it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) startInformersAndController() }) @@ -1636,8 +1675,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostname = "fake.example.com" var caCrt []byte it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) startInformersAndController() r.NoError(runControllerSync()) @@ -1662,7 +1705,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { newCASecret := newActualCASecret(anotherCA, caSecretName) caCrt = newCASecret.Data["ca.crt"] addSecretToTrackers(newCASecret, kubeAPIClient) - addObjectToInformerAndWait(newCASecret, kubeInformers.Core().V1().Secrets()) + addObjectToKubeInformerAndWait(newCASecret, kubeInformers.Core().V1().Secrets()) }) it("deletes the old TLS cert and makes a new TLS cert using the new CA", func() { @@ -1700,7 +1743,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the configuration switches from enabled to disabled mode", func() { it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1720,8 +1767,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - // Update the configmap. - updateImpersonatorConfigMapInInformerAndWait(configMapResourceName, "mode: disabled", kubeInformers.Core().V1().ConfigMaps()) + // Update the CredentialIssuer. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) r.NoError(runControllerSync()) requireTLSServerIsNoLongerRunning() @@ -1733,8 +1784,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { deleteServiceFromTracker(loadBalancerServiceName, kubeInformerClient) waitForObjectToBeDeletedFromInformer(loadBalancerServiceName, kubeInformers.Core().V1().Services()) - // Update the configmap again. - updateImpersonatorConfigMapInInformerAndWait(configMapResourceName, "mode: enabled", kubeInformers.Core().V1().ConfigMaps()) + // Update the CredentialIssuer again. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) r.NoError(runControllerSync()) requireTLSServerIsRunningWithoutCerts() @@ -1747,8 +1802,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the endpoint switches from specified, to not specified, to specified again", func() { it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", localhostIP) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1770,7 +1829,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) // Switch to "enabled" mode without an "endpoint", so a load balancer is needed now. - updateImpersonatorConfigMapInInformerAndWait(configMapResourceName, "mode: enabled", kubeInformers.Core().V1().ConfigMaps()) + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 5) @@ -1807,8 +1870,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[5], kubeInformers.Core().V1().Secrets()) // Now switch back to having the "endpoint" specified, so the load balancer is not needed anymore. - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", localhostIP) - updateImpersonatorConfigMapInInformerAndWait(configMapResourceName, configMapYAML, kubeInformers.Core().V1().ConfigMaps()) + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 9) @@ -2077,14 +2144,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the configmap is invalid", func() { + when("the CredentialIssuer is invalid", func() { it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "not yaml", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: "not-valid", + }, + }, pinnipedInformerClient) }) it("returns an error", func() { startInformersAndController() - errString := "invalid impersonator configuration: decode yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type impersonator.Config" + errString := `invalid impersonator configuration: invalid impersonation proxy mode "not-valid", valid values are auto, disabled, or enabled` r.EqualError(runControllerSync(), errString) requireCredentialIssuer(newErrorStrategy(errString)) requireSigningCertProviderIsEmpty() @@ -2111,7 +2182,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("there is an error creating the tls secret", func() { it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "{mode: enabled, endpoint: example.com}", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "example.com", + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) kubeAPIClient.PrependReactor("create", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { createdSecret := action.(coretesting.CreateAction).GetObject().(*corev1.Secret) @@ -2137,7 +2213,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("there is an error creating the CA secret", func() { it.Before(func() { - addImpersonatorConfigMapToTracker(configMapResourceName, "{mode: enabled, endpoint: example.com}", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "example.com", + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) kubeAPIClient.PrependReactor("create", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { createdSecret := action.(coretesting.CreateAction).GetObject().(*corev1.Secret) @@ -2163,7 +2244,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CA secret exists but is invalid while the TLS secret needs to be created", func() { it.Before(func() { addNodeWithRoleToTracker("control-plane", kubeAPIClient) - addImpersonatorConfigMapToTracker(configMapResourceName, "{mode: enabled, endpoint: example.com}", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "example.com", + }, + }, pinnipedInformerClient) addSecretToTrackers(newEmptySecret(caSecretName), kubeAPIClient, kubeInformerClient) }) @@ -2207,7 +2293,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addNodeWithRoleToTracker("control-plane", kubeAPIClient) addSecretToTrackers(newEmptySecret(tlsSecretName), kubeInformerClient) - addImpersonatorConfigMapToTracker(configMapResourceName, "{mode: disabled}", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, pinnipedInformerClient) }) it("does not pass the not found error through", func() { @@ -2225,8 +2315,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the PEM formatted data in the TLS Secret is not a valid cert", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", localhostIP) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) tlsSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -2281,7 +2375,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var caCrt []byte it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled", kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) ca := newCA() caSecret := newActualCASecret(ca, caSecretName) @@ -2330,7 +2428,6 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var caCrt []byte it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled", kubeInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) ca := newCA() caSecret := newActualCASecret(ca, caSecretName) @@ -2408,8 +2505,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the impersonator is ready but there is a problem with the signing secret, which should be created by another controller", func() { const fakeHostname = "foo.example.com" it.Before(func() { - configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname) - addImpersonatorConfigMapToTracker(configMapResourceName, configMapYAML, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + }, + }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index acd5bee7..3aa75c39 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -251,11 +251,10 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { WithController( impersonatorconfig.NewImpersonatorConfigController( c.ServerInstallationInfo.Namespace, - c.NamesConfig.ImpersonationConfigMap, c.NamesConfig.CredentialIssuer, client.Kubernetes, client.PinnipedConcierge, - informers.installationNamespaceK8s.Core().V1().ConfigMaps(), + informers.pinniped.Config().V1alpha1().CredentialIssuers(), informers.installationNamespaceK8s.Core().V1().Services(), informers.installationNamespaceK8s.Core().V1().Secrets(), controllerlib.WithInformer, From 9af3cb1115bd2eec8a332384593ffb243965ce97 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Tue, 18 May 2021 09:51:11 -0700 Subject: [PATCH 06/43] Change impersonation integration test to use CredentialIssuer spec rather than a configmap Signed-off-by: Margo Crawford --- .../concierge_impersonation_proxy_test.go | 98 ++++++++----------- 1 file changed, 40 insertions(+), 58 deletions(-) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index faf44e95..6e7153c6 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -51,13 +51,12 @@ import ( "k8s.io/client-go/util/cert" "k8s.io/client-go/util/certificate/csr" "k8s.io/client-go/util/keyutil" - "sigs.k8s.io/yaml" + "k8s.io/client-go/util/retry" conciergev1alpha "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1" loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1" pinnipedconciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned" - "go.pinniped.dev/internal/concierge/impersonator" "go.pinniped.dev/internal/httputil/roundtripper" "go.pinniped.dev/internal/kubeclient" "go.pinniped.dev/internal/testutil" @@ -159,33 +158,25 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl return mostRecentTokenCredentialRequestResponse.Status.Credential } - oldConfigMap, err := adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Get(ctx, impersonationProxyConfigMapName(env), metav1.GetOptions{}) - if !k8serrors.IsNotFound(err) { - require.NoError(t, err) // other errors aside from NotFound are unexpected - t.Logf("stashing a pre-existing configmap %s", oldConfigMap.Name) - require.NoError(t, adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Delete(ctx, impersonationProxyConfigMapName(env), metav1.DeleteOptions{})) - } - // At the end of the test, clean up the ConfigMap. + oldCredentialIssuer, err := adminConciergeClient.ConfigV1alpha1().CredentialIssuers().Get(ctx, credentialIssuerName(env), metav1.GetOptions{}) + require.NoError(t, err) + // At the end of the test, clean up the CredentialIssuer t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() // Delete any version that was created by this test. - t.Logf("cleaning up configmap at end of test %s", impersonationProxyConfigMapName(env)) - err := adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Delete(ctx, impersonationProxyConfigMapName(env), metav1.DeleteOptions{}) - if !k8serrors.IsNotFound(err) { - require.NoError(t, err) // only not found errors are acceptable - } - - // Only recreate it if it already existed at the start of this test. - if len(oldConfigMap.Data) != 0 { - t.Log(oldConfigMap) - oldConfigMap.UID = "" // cant have a UID yet - oldConfigMap.ResourceVersion = "" - t.Logf("restoring a pre-existing configmap %s", oldConfigMap.Name) - _, err = adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Create(ctx, oldConfigMap, metav1.CreateOptions{}) - require.NoError(t, err) - } + t.Logf("cleaning up credentialissuer at end of test %s", credentialIssuerName(env)) + err = retry.RetryOnConflict(retry.DefaultRetry, func() error { + newCredentialIssuer, err := adminConciergeClient.ConfigV1alpha1().CredentialIssuers().Get(ctx, credentialIssuerName(env), metav1.GetOptions{}) + if err != nil { + return err + } + oldCredentialIssuer.Spec.DeepCopyInto(&newCredentialIssuer.Spec) + _, err = adminConciergeClient.ConfigV1alpha1().CredentialIssuers().Update(ctx, newCredentialIssuer, metav1.UpdateOptions{}) + return err + }) + require.NoError(t, err) // If we are running on an environment that has a load balancer, expect that the // CredentialIssuer will be updated eventually with a successful impersonation proxy frontend. @@ -221,10 +212,11 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl requireDisabledStrategy(ctx, t, env, adminConciergeClient) // Create configuration to make the impersonation proxy turn on with no endpoint (i.e. automatically create a load balancer). - configMap := impersonationProxyConfigMapForConfig(t, env, impersonator.Config{Mode: impersonator.ModeEnabled}) - t.Logf("creating configmap %s", configMap.Name) - _, err = adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Create(ctx, &configMap, metav1.CreateOptions{}) - require.NoError(t, err) + updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ + ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + Mode: conciergev1alpha.ImpersonationProxyModeEnabled, + }, + }) default: // Auto mode should have decided that the impersonator will be disabled. We need to manually enable it. @@ -246,13 +238,12 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl require.Truef(t, isErr, "wanted error %q to be service unavailable via squid error, but: %s", err, message) // Create configuration to make the impersonation proxy turn on with a hard coded endpoint (without a load balancer). - configMap := impersonationProxyConfigMapForConfig(t, env, impersonator.Config{ - Mode: impersonator.ModeEnabled, - Endpoint: proxyServiceEndpoint, + updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ + ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + Mode: conciergev1alpha.ImpersonationProxyModeEnabled, + ExternalEndpoint: proxyServiceEndpoint, + }, }) - t.Logf("creating configmap %s", configMap.Name) - _, err = adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Create(ctx, &configMap, metav1.CreateOptions{}) - require.NoError(t, err) } // At this point the impersonator should be starting/running. When it is ready, the CredentialIssuer's @@ -1183,16 +1174,11 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl t.Run("manually disabling the impersonation proxy feature", func(t *testing.T) { // Update configuration to force the proxy to disabled mode - configMap := impersonationProxyConfigMapForConfig(t, env, impersonator.Config{Mode: impersonator.ModeDisabled}) - if clusterSupportsLoadBalancers { - t.Logf("creating configmap %s", configMap.Name) - _, err := adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Create(ctx, &configMap, metav1.CreateOptions{}) - require.NoError(t, err) - } else { - t.Logf("updating configmap %s", configMap.Name) - _, err := adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Update(ctx, &configMap, metav1.UpdateOptions{}) - require.NoError(t, err) - } + updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ + ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + Mode: conciergev1alpha.ImpersonationProxyModeDisabled, + }, + }) if clusterSupportsLoadBalancers { // The load balancer should have been deleted when we disabled the impersonation proxy. @@ -1450,19 +1436,19 @@ func kubeconfigProxyFunc(t *testing.T, squidProxyURL string) func(req *http.Requ } } -func impersonationProxyConfigMapForConfig(t *testing.T, env *library.TestEnv, config impersonator.Config) corev1.ConfigMap { +func updateCredentialIssuer(ctx context.Context, t *testing.T, env *library.TestEnv, adminConciergeClient pinnipedconciergeclientset.Interface, spec conciergev1alpha.CredentialIssuerSpec) { t.Helper() - configString, err := yaml.Marshal(config) + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + newCredentialIssuer, err := adminConciergeClient.ConfigV1alpha1().CredentialIssuers().Get(ctx, credentialIssuerName(env), metav1.GetOptions{}) + if err != nil { + return err + } + spec.DeepCopyInto(&newCredentialIssuer.Spec) + _, err = adminConciergeClient.ConfigV1alpha1().CredentialIssuers().Update(ctx, newCredentialIssuer, metav1.UpdateOptions{}) + return err + }) require.NoError(t, err) - configMap := corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: impersonationProxyConfigMapName(env), - }, - Data: map[string]string{ - "config.yaml": string(configString), - }} - return configMap } func hasImpersonationProxyLoadBalancerService(ctx context.Context, env *library.TestEnv, client kubernetes.Interface) (bool, error) { @@ -1476,10 +1462,6 @@ func hasImpersonationProxyLoadBalancerService(ctx context.Context, env *library. return service.Spec.Type == corev1.ServiceTypeLoadBalancer, nil } -func impersonationProxyConfigMapName(env *library.TestEnv) string { - return env.ConciergeAppName + "-impersonation-proxy-config" -} - func impersonationProxyTLSSecretName(env *library.TestEnv) string { return env.ConciergeAppName + "-impersonation-proxy-tls-serving-certificate" } From 51f1a0ec13a2171977d1a968044bba8421ed1767 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Tue, 18 May 2021 12:16:27 -0700 Subject: [PATCH 07/43] WIP: not using impersonator.config just credentialissuer directly Signed-off-by: Matt Moyer --- internal/concierge/impersonator/config.go | 83 -- .../concierge/impersonator/config_test.go | 155 --- .../impersonatorconfig/impersonator_config.go | 73 +- .../impersonator_config_test.go | 1217 +++++++++-------- 4 files changed, 709 insertions(+), 819 deletions(-) delete mode 100644 internal/concierge/impersonator/config.go delete mode 100644 internal/concierge/impersonator/config_test.go diff --git a/internal/concierge/impersonator/config.go b/internal/concierge/impersonator/config.go deleted file mode 100644 index 04137835..00000000 --- a/internal/concierge/impersonator/config.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2021 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package impersonator - -import ( - "fmt" - - v1 "k8s.io/api/core/v1" - "sigs.k8s.io/yaml" - - "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" -) - -type Mode string - -const ( - // Explicitly enable the impersonation proxy. - ModeEnabled Mode = "enabled" - - // Explicitly disable the impersonation proxy. - ModeDisabled Mode = "disabled" - - // Allow the proxy to decide if it should be enabled or disabled based upon the cluster in which it is running. - ModeAuto Mode = "auto" -) - -const ( - ConfigMapDataKey = "config.yaml" -) - -type Config struct { - // Enable or disable the impersonation proxy. Optional. Defaults to ModeAuto. - Mode Mode `json:"mode,omitempty"` - - // Used when creating TLS certificates and for clients to discover the endpoint. Optional. When not specified, if the - // impersonation proxy is started, then it will automatically create a LoadBalancer Service and use its ingress as the - // endpoint. - // - // When specified, it may be a hostname or IP address, optionally with a port number, of the impersonation proxy - // for clients to use from outside the cluster. E.g. myhost.mycompany.com:8443. Clients should assume that they should - // connect via HTTPS to this service. - Endpoint string `json:"endpoint,omitempty"` -} - -func (c *Config) HasEndpoint() bool { - return c.Endpoint != "" -} - -func NewConfig() *Config { - return &Config{Mode: ModeAuto} -} - -func ConfigFromConfigMap(configMap *v1.ConfigMap) (*Config, error) { - stringConfig, ok := configMap.Data[ConfigMapDataKey] - if !ok { - return nil, fmt.Errorf(`ConfigMap is missing expected key "%s"`, ConfigMapDataKey) - } - config := NewConfig() - if err := yaml.Unmarshal([]byte(stringConfig), config); err != nil { - return nil, fmt.Errorf("decode yaml: %w", err) - } - if config.Mode != ModeAuto && config.Mode != ModeEnabled && config.Mode != ModeDisabled { - return nil, fmt.Errorf(`illegal value for "mode": %s`, config.Mode) - } - return config, nil -} - -func ConfigFromCredentialIssuer(credIssuer *v1alpha1.CredentialIssuer) (*Config, error) { - config := NewConfig() - switch mode := credIssuer.Spec.ImpersonationProxy.Mode; mode { - case v1alpha1.ImpersonationProxyModeAuto: - config.Mode = ModeAuto - case v1alpha1.ImpersonationProxyModeDisabled: - config.Mode = ModeDisabled - case v1alpha1.ImpersonationProxyModeEnabled: - config.Mode = ModeEnabled - default: - return nil, fmt.Errorf("invalid impersonation proxy mode %q, valid values are auto, disabled, or enabled", mode) - } - config.Endpoint = credIssuer.Spec.ImpersonationProxy.ExternalEndpoint - return config, nil -} diff --git a/internal/concierge/impersonator/config_test.go b/internal/concierge/impersonator/config_test.go deleted file mode 100644 index b734dba9..00000000 --- a/internal/concierge/impersonator/config_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2021 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package impersonator - -import ( - "testing" - - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "go.pinniped.dev/internal/here" -) - -func TestNewConfig(t *testing.T) { - // It defaults the mode. - require.Equal(t, &Config{Mode: ModeAuto}, NewConfig()) -} - -func TestHasEndpoint(t *testing.T) { - configWithoutEndpoint := Config{} - configWithEndpoint := Config{Endpoint: "something"} - require.False(t, configWithoutEndpoint.HasEndpoint()) - require.True(t, configWithEndpoint.HasEndpoint()) -} - -func TestConfigFromConfigMap(t *testing.T) { - tests := []struct { - name string - configMap *v1.ConfigMap - wantConfig *Config - wantError string - }{ - { - name: "fully configured, valid config", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "config.yaml": here.Doc(` - mode: enabled - endpoint: proxy.example.com:8443 - `), - }, - }, - wantConfig: &Config{ - Mode: "enabled", - Endpoint: "proxy.example.com:8443", - }, - }, - { - name: "empty, valid config", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "config.yaml": "", - }, - }, - wantConfig: &Config{ - Mode: "auto", - Endpoint: "", - }, - }, - { - name: "valid config with mode enabled", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "config.yaml": "mode: enabled", - }, - }, - wantConfig: &Config{ - Mode: "enabled", - Endpoint: "", - }, - }, - { - name: "valid config with mode disabled", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "config.yaml": "mode: disabled", - }, - }, - wantConfig: &Config{ - Mode: "disabled", - Endpoint: "", - }, - }, - { - name: "valid config with mode auto", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "config.yaml": "mode: auto", - }, - }, - wantConfig: &Config{ - Mode: "auto", - Endpoint: "", - }, - }, - { - name: "wrong key in configmap", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "wrong-key": "", - }, - }, - wantError: `ConfigMap is missing expected key "config.yaml"`, - }, - { - name: "illegal yaml in configmap", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "config.yaml": "this is not yaml", - }, - }, - wantError: "decode yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type impersonator.Config", - }, - { - name: "illegal value for mode in configmap", - configMap: &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Data: map[string]string{ - "config.yaml": "mode: unexpected-value", - }, - }, - wantError: `illegal value for "mode": unexpected-value`, - }, - } - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - config, err := ConfigFromConfigMap(test.configMap) - require.Equal(t, test.wantConfig, config) - if test.wantError != "" { - require.EqualError(t, err, test.wantError) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 5db9b4ba..41337f90 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -259,55 +259,53 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v return credentialIssuerStrategyResult, nil } -func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*impersonator.Config, error) { +func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*v1alpha1.ImpersonationProxySpec, error) { credIssuer, err := c.credIssuerInformer.Lister().Get(c.credentialIssuerResourceName) - if k8serrors.IsNotFound(err) { - plog.Info("Did not find impersonation proxy config: using default config values", - "credentialIssuer", c.credentialIssuerResourceName, - ) - return impersonator.NewConfig(), nil - } - if err != nil { return nil, fmt.Errorf("failed to get %s CredentialIssuer: %w", c.credentialIssuerResourceName, err) } - config, err := impersonator.ConfigFromCredentialIssuer(credIssuer) + credIssuer = credIssuer.DeepCopy() + err = validateCredentialIssuerSpec(credIssuer) if err != nil { return nil, fmt.Errorf("invalid impersonator configuration: %v", err) } plog.Info("Read impersonation proxy config", "credentialIssuer", c.credentialIssuerResourceName, ) - return config, nil + if credIssuer.Spec.ImpersonationProxy.Service.Type == "" { + credIssuer.Spec.ImpersonationProxy.Service.Type = v1alpha1.ImpersonationProxyServiceTypeLoadBalancer + } + return &credIssuer.Spec.ImpersonationProxy, nil } -func (c *impersonatorConfigController) shouldHaveImpersonator(config *impersonator.Config) bool { - return c.enabledByAutoMode(config) || config.Mode == impersonator.ModeEnabled +func (c *impersonatorConfigController) shouldHaveImpersonator(config *v1alpha1.ImpersonationProxySpec) bool { + return c.enabledByAutoMode(config) || config.Mode == v1alpha1.ImpersonationProxyModeEnabled } -func (c *impersonatorConfigController) enabledByAutoMode(config *impersonator.Config) bool { - return config.Mode == impersonator.ModeAuto && !*c.hasControlPlaneNodes +func (c *impersonatorConfigController) enabledByAutoMode(config *v1alpha1.ImpersonationProxySpec) bool { + return config.Mode == v1alpha1.ImpersonationProxyModeAuto && !*c.hasControlPlaneNodes } -func (c *impersonatorConfigController) disabledByAutoMode(config *impersonator.Config) bool { - return config.Mode == impersonator.ModeAuto && *c.hasControlPlaneNodes +func (c *impersonatorConfigController) disabledByAutoMode(config *v1alpha1.ImpersonationProxySpec) bool { + return config.Mode == v1alpha1.ImpersonationProxyModeAuto && *c.hasControlPlaneNodes } -func (c *impersonatorConfigController) disabledExplicitly(config *impersonator.Config) bool { - return config.Mode == impersonator.ModeDisabled +func (c *impersonatorConfigController) disabledExplicitly(config *v1alpha1.ImpersonationProxySpec) bool { + return config.Mode == v1alpha1.ImpersonationProxyModeDisabled } -func (c *impersonatorConfigController) shouldHaveLoadBalancer(config *impersonator.Config) bool { - return c.shouldHaveImpersonator(config) && !config.HasEndpoint() +func (c *impersonatorConfigController) shouldHaveLoadBalancer(config *v1alpha1.ImpersonationProxySpec) bool { + return c.shouldHaveImpersonator(config) && config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeLoadBalancer } -func (c *impersonatorConfigController) shouldHaveTLSSecret(config *impersonator.Config) bool { +func (c *impersonatorConfigController) shouldHaveTLSSecret(config *v1alpha1.ImpersonationProxySpec) bool { return c.shouldHaveImpersonator(config) } func (c *impersonatorConfigController) updateStrategy(ctx context.Context, strategy *v1alpha1.CredentialIssuerStrategy) error { + // TODO use informer client rather than api client for reading return issuerconfig.UpdateStrategy(ctx, c.credentialIssuerResourceName, c.labels, c.pinnipedAPIClient, *strategy) } @@ -652,15 +650,25 @@ func (c *impersonatorConfigController) createCASecret(ctx context.Context) (*cer return impersonationCA, nil } -func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *impersonator.Config) (*certNameInfo, error) { - if config.HasEndpoint() { +func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1alpha1.ImpersonationProxySpec) (*certNameInfo, error) { + // possible valid options: + // - you have a loadbalancer and are autoconfiguring the endpoint -> get cert info based on load balancer ip/hostnome + // - you have a loadbalancer AND an external endpoint -> either should work since they should be the same + // - external endpoint no loadbalancer or other service -> use the endpoint config + // - external endpoint and ClusterIP -> use external endpoint? + // + // - is it legal to have a clusterip and no external endpoint??? + if config.ExternalEndpoint != "" { return c.findTLSCertificateNameFromEndpointConfig(config), nil + } else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { + // c.findTLSCertificateNameFromClusterIPService() + // TODO implement this } return c.findTLSCertificateNameFromLoadBalancer() } -func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig(config *impersonator.Config) *certNameInfo { - endpointMaybeWithPort := config.Endpoint +func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig(config *v1alpha1.ImpersonationProxySpec) *certNameInfo { + endpointMaybeWithPort := config.ExternalEndpoint endpointWithoutPort := strings.Split(endpointMaybeWithPort, ":")[0] parsedAsIP := net.ParseIP(endpointWithoutPort) if parsedAsIP != nil { @@ -820,7 +828,7 @@ func (c *impersonatorConfigController) clearSignerCA() { c.impersonationSigningCertProvider.UnsetCertKeyContent() } -func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, config *impersonator.Config, ca *certauthority.CA) *v1alpha1.CredentialIssuerStrategy { +func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, config *v1alpha1.ImpersonationProxySpec, ca *certauthority.CA) *v1alpha1.CredentialIssuerStrategy { switch { case c.disabledExplicitly(config): return &v1alpha1.CredentialIssuerStrategy{ @@ -863,3 +871,16 @@ func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, conf } } } + +func validateCredentialIssuerSpec(credIssuer *v1alpha1.CredentialIssuer) error { + // TODO check external endpoint for valid ip or hostname + // TODO if service type is none and externalendpoint is "" return error + switch mode := credIssuer.Spec.ImpersonationProxy.Mode; mode { + case v1alpha1.ImpersonationProxyModeAuto: + case v1alpha1.ImpersonationProxyModeDisabled: + case v1alpha1.ImpersonationProxyModeEnabled: + default: + return fmt.Errorf("invalid impersonation proxy mode %q, valid values are auto, disabled, or enabled", mode) + } + return nil +} diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 7fa569a8..057969ed 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -980,11 +980,87 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { closeTestHTTPServer() }) - when("the CredentialIssuer does not yet exist or it was deleted (defaults to auto mode)", func() { + when("the CredentialIssuer does not yet exist or it was deleted (sync returns an error)", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) }) + when("there are visible control plane nodes and a loadbalancer and a tls Secret", func() { + it.Before(func() { + addNodeWithRoleToTracker("control-plane", kubeAPIClient) + addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) + addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) + addSecretToTrackers(newEmptySecret(tlsSecretName), kubeAPIClient, kubeInformerClient) + }) + + it("errors and does nothing else", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "failed to get some-credential-issuer-resource-name CredentialIssuer: credentialissuer.config.concierge.pinniped.dev \"some-credential-issuer-resource-name\" not found") + requireTLSServerWasNeverStarted() + r.Len(kubeAPIClient.Actions(), 0) + }) + }) + }) + + when("the configuration is auto mode with an endpoint and service type none", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + }) + + when("there are visible control plane nodes", func() { + it.Before(func() { + addNodeWithRoleToTracker("control-plane", kubeAPIClient) + }) + + it("does not start the impersonator", func() { + startInformersAndController() + r.NoError(runControllerSync()) + requireTLSServerWasNeverStarted() + requireNodesListed(kubeAPIClient.Actions()[0]) + r.Len(kubeAPIClient.Actions(), 1) + requireCredentialIssuer(newAutoDisabledStrategy()) + requireSigningCertProviderIsEmpty() + }) + }) + + when("there are not visible control plane nodes", func() { + it.Before(func() { + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator according to the settings in the CredentialIssuer", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + }) + + when("the configuration is auto mode", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) + }) + when("there are visible control plane nodes", func() { it.Before(func() { addNodeWithRoleToTracker("control-plane", kubeAPIClient) @@ -1271,628 +1347,619 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireSigningCertProviderIsEmpty() }) }) + }) - when("the CredentialIssuer is already present", func() { + when("the configuration is disabled mode", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("does not start the impersonator", func() { + startInformersAndController() + r.NoError(runControllerSync()) + requireTLSServerWasNeverStarted() + requireNodesListed(kubeAPIClient.Actions()[0]) + r.Len(kubeAPIClient.Actions(), 1) + requireCredentialIssuer(newManuallyDisabledStrategy()) + requireSigningCertProviderIsEmpty() + }) + }) + + when("the configuration is enabled mode", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) }) - - when("the configuration is auto mode with an endpoint", func() { - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, - ExternalEndpoint: localhostIP, - }, - }, pinnipedInformerClient) - }) - - when("there are visible control plane nodes", func() { - it.Before(func() { - addNodeWithRoleToTracker("control-plane", kubeAPIClient) - }) - - it("does not start the impersonator", func() { - startInformersAndController() - r.NoError(runControllerSync()) - requireTLSServerWasNeverStarted() - requireNodesListed(kubeAPIClient.Actions()[0]) - r.Len(kubeAPIClient.Actions(), 1) - requireCredentialIssuer(newAutoDisabledStrategy()) - requireSigningCertProviderIsEmpty() - }) - }) - - when("there are not visible control plane nodes", func() { - it.Before(func() { - addNodeWithRoleToTracker("worker", kubeAPIClient) - }) - - it("starts the impersonator according to the settings in the CredentialIssuer", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - requireTLSServerIsRunning(ca, testServerAddr(), nil) - requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - }) - - when("the configuration is disabled mode", func() { - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeDisabled, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - }) - - it("does not start the impersonator", func() { - startInformersAndController() - r.NoError(runControllerSync()) - requireTLSServerWasNeverStarted() - requireNodesListed(kubeAPIClient.Actions()[0]) - r.Len(kubeAPIClient.Actions(), 1) - requireCredentialIssuer(newManuallyDisabledStrategy()) - requireSigningCertProviderIsEmpty() - }) - }) - - when("the configuration is enabled mode", func() { - when("no load balancer", func() { - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("control-plane", kubeAPIClient) - }) - - it("starts the impersonator and creates a load balancer", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) - requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) - requireSigningCertProviderIsEmpty() - }) - - it("returns an error when the impersonation TLS server fails to start", func() { - impersonatorFuncError = errors.New("impersonation server start error") - startInformersAndController() - r.EqualError(runControllerSync(), "impersonation server start error") - requireCredentialIssuer(newErrorStrategy("impersonation server start error")) - requireSigningCertProviderIsEmpty() - }) - }) - - when("a loadbalancer already exists", func() { - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) - addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) - }) - - it("starts the impersonator without creating a load balancer", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 2) - requireNodesListed(kubeAPIClient.Actions()[0]) - requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) - requireSigningCertProviderIsEmpty() - }) - - it("returns an error when the impersonation TLS server fails to start", func() { - impersonatorFuncError = errors.New("impersonation server start error") - startInformersAndController() - r.EqualError(runControllerSync(), "impersonation server start error") - requireCredentialIssuer(newErrorStrategy("impersonation server start error")) - requireSigningCertProviderIsEmpty() - }) - }) - - when("a load balancer and a secret already exists", func() { - var caCrt []byte - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - ca := newCA() - caSecret := newActualCASecret(ca, caSecretName) - caCrt = caSecret.Data["ca.crt"] - addSecretToTrackers(caSecret, kubeAPIClient, kubeInformerClient) - tlsSecret := newActualTLSSecret(ca, tlsSecretName, localhostIP) - addSecretToTrackers(tlsSecret, kubeAPIClient, kubeInformerClient) - addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeInformerClient) - addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeAPIClient) - }) - - it("starts the impersonator with the existing tls certs, does not start loadbalancer or make tls secret", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 1) - requireNodesListed(kubeAPIClient.Actions()[0]) - requireTLSServerIsRunning(caCrt, testServerAddr(), nil) - requireCredentialIssuer(newSuccessStrategy(localhostIP, caCrt)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - - when("the CredentialIssuer has a hostname specified for the endpoint", func() { - const fakeHostname = "fake.example.com" - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - }) - - it("starts the impersonator, generates a valid cert for the specified hostname", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - - when("the CredentialIssuer has a endpoint which is an IP address with a port", func() { - const fakeIPWithPort = "127.0.0.1:3000" - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeIPWithPort, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - }) - - it("starts the impersonator, generates a valid cert for the specified IP address", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeIPWithPort. - requireTLSServerIsRunning(ca, fakeIPWithPort, map[string]string{fakeIPWithPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeIPWithPort, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - - when("the CredentialIssuer has a endpoint which is a hostname with a port", func() { - const fakeHostnameWithPort = "fake.example.com:3000" - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostnameWithPort, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - }) - - it("starts the impersonator, generates a valid cert for the specified hostname", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeHostnameWithPort. - requireTLSServerIsRunning(ca, fakeHostnameWithPort, map[string]string{fakeHostnameWithPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostnameWithPort, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - - when("switching the CredentialIssuer from ip address endpoint to hostname endpoint and back to ip address", func() { - const fakeHostname = "fake.example.com" - const fakeIP = "127.0.0.42" - - var hostnameConfig = v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - }, - } - var ipAddressConfig = v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeIP, - }, - } - - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, ipAddressConfig, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - }) - - it("regenerates the cert for the hostname, then regenerates it for the IP again", func() { - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeIP. - requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeIP, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Secrets()) - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - - // Switch the endpoint config to a hostname. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, hostnameConfig, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) - - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 5) - requireTLSSecretWasDeleted(kubeAPIClient.Actions()[3]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[4], ca) // reuses the old CA - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - - // Simulate the informer cache's background update from its watch. - deleteSecretFromTracker(tlsSecretName, kubeInformerClient) - waitForObjectToBeDeletedFromInformer(tlsSecretName, kubeInformers.Core().V1().Secrets()) - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[4], kubeInformers.Core().V1().Secrets()) - - // Switch the endpoint config back to an IP. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, ipAddressConfig, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) - - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 7) - requireTLSSecretWasDeleted(kubeAPIClient.Actions()[5]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[6], ca) // reuses the old CA again - // Check that the server is running and that TLS certs that are being served are are for fakeIP. - requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeIP, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - - when("the TLS cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { - const fakeHostname = "fake.example.com" - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - startInformersAndController() - }) - - it("uses the existing CA cert the make a new TLS cert", func() { - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Secrets()) - - // Delete the TLS Secret that was just created from the Kube API server. Note that we never - // simulated it getting added to the informer cache, so we don't need to remove it from there. - deleteSecretFromTracker(tlsSecretName, kubeAPIClient) - - // Run again. It should create a new TLS cert using the old CA cert. - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 4) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - - when("the CA cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { - const fakeHostname = "fake.example.com" - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - startInformersAndController() - }) - - it("makes a new CA cert, deletes the old TLS cert, and makes a new TLS cert using the new CA", func() { - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - - // Delete the CA Secret that was just created from the Kube API server. Note that we never - // simulated it getting added to the informer cache, so we don't need to remove it from there. - deleteSecretFromTracker(caSecretName, kubeAPIClient) - - // Run again. It should create both a new CA cert and a new TLS cert using the new CA cert. - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 6) - ca = requireCASecretWasCreated(kubeAPIClient.Actions()[3]) - requireTLSSecretWasDeleted(kubeAPIClient.Actions()[4]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[5], ca) // created using the new CA - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - }) - - when("the CA cert is overwritten by another valid CA cert", func() { - const fakeHostname = "fake.example.com" - var caCrt []byte - it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) - startInformersAndController() - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - - // Simulate someone updating the CA Secret out of band, e.g. when a human edits it with kubectl. - // Delete the CA Secret that was just created from the Kube API server. Note that we never - // simulated it getting added to the informer cache, so we don't need to remove it from there. - // Then add a new one. Delete + new = update, since only the final state is observed. - deleteSecretFromTracker(caSecretName, kubeAPIClient) - anotherCA := newCA() - newCASecret := newActualCASecret(anotherCA, caSecretName) - caCrt = newCASecret.Data["ca.crt"] - addSecretToTrackers(newCASecret, kubeAPIClient) - addObjectToKubeInformerAndWait(newCASecret, kubeInformers.Core().V1().Secrets()) - }) - - it("deletes the old TLS cert and makes a new TLS cert using the new CA", func() { - // Run again. It should use the updated CA cert to create a new TLS cert. - r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 5) - requireTLSSecretWasDeleted(kubeAPIClient.Actions()[3]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[4], caCrt) // created using the updated CA - // Check that the server is running and that TLS certs that are being served are are for fakeHostname. - requireTLSServerIsRunning(caCrt, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) - requireCredentialIssuer(newSuccessStrategy(fakeHostname, caCrt)) - requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) - }) - - when("deleting the TLS cert due to mismatched CA results in an error", func() { - it.Before(func() { - kubeAPIClient.PrependReactor("delete", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - if action.(coretesting.DeleteAction).GetName() == tlsSecretName { - return true, nil, fmt.Errorf("error on tls secret delete") - } - return false, nil, nil - }) - }) - - it("returns an error", func() { - r.Error(runControllerSync(), "error on tls secret delete") - r.Len(kubeAPIClient.Actions(), 4) - requireTLSSecretWasDeleted(kubeAPIClient.Actions()[3]) // tried to delete cert but failed - requireCredentialIssuer(newErrorStrategy("error on tls secret delete")) - requireSigningCertProviderIsEmpty() - }) - }) - }) - }) - - when("the configuration switches from enabled to disabled mode", func() { + when("no load balancer", func() { it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformerClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) + addNodeWithRoleToTracker("control-plane", kubeAPIClient) }) - it("starts the impersonator and loadbalancer, then shuts it down, then starts it again", func() { + it("starts the impersonator and creates a load balancer", func() { startInformersAndController() - r.NoError(runControllerSync()) - requireTLSServerIsRunningWithoutCerts() r.Len(kubeAPIClient.Actions(), 3) requireNodesListed(kubeAPIClient.Actions()[0]) requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireCredentialIssuer(newPendingStrategy()) - requireSigningCertProviderIsEmpty() - - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - - // Update the CredentialIssuer. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeDisabled, - }, - }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) - - r.NoError(runControllerSync()) - requireTLSServerIsNoLongerRunning() - r.Len(kubeAPIClient.Actions(), 4) - requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[3]) - requireCredentialIssuer(newManuallyDisabledStrategy()) - requireSigningCertProviderIsEmpty() - - deleteServiceFromTracker(loadBalancerServiceName, kubeInformerClient) - waitForObjectToBeDeletedFromInformer(loadBalancerServiceName, kubeInformers.Core().V1().Services()) - - // Update the CredentialIssuer again. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - }, - }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) - - r.NoError(runControllerSync()) requireTLSServerIsRunningWithoutCerts() - r.Len(kubeAPIClient.Actions(), 5) - requireLoadBalancerWasCreated(kubeAPIClient.Actions()[4]) requireCredentialIssuer(newPendingStrategy()) requireSigningCertProviderIsEmpty() }) + + it("returns an error when the impersonation TLS server fails to start", func() { + impersonatorFuncError = errors.New("impersonation server start error") + startInformersAndController() + r.EqualError(runControllerSync(), "impersonation server start error") + requireCredentialIssuer(newErrorStrategy("impersonation server start error")) + requireSigningCertProviderIsEmpty() + }) }) - when("the endpoint switches from specified, to not specified, to specified again", func() { + when("a loadbalancer already exists", func() { + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) + addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) + }) + + it("starts the impersonator without creating a load balancer", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 2) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSServerIsRunningWithoutCerts() + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + }) + + it("returns an error when the impersonation TLS server fails to start", func() { + impersonatorFuncError = errors.New("impersonation server start error") + startInformersAndController() + r.EqualError(runControllerSync(), "impersonation server start error") + requireCredentialIssuer(newErrorStrategy("impersonation server start error")) + requireSigningCertProviderIsEmpty() + }) + }) + + when("a load balancer and a secret already exists", func() { + var caCrt []byte + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + ca := newCA() + caSecret := newActualCASecret(ca, caSecretName) + caCrt = caSecret.Data["ca.crt"] + addSecretToTrackers(caSecret, kubeAPIClient, kubeInformerClient) + tlsSecret := newActualTLSSecret(ca, tlsSecretName, localhostIP) + addSecretToTrackers(tlsSecret, kubeAPIClient, kubeInformerClient) + addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeInformerClient) + addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeAPIClient) + }) + + it("starts the impersonator with the existing tls certs, does not start loadbalancer or make tls secret", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 1) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireTLSServerIsRunning(caCrt, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, caCrt)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + + when("the CredentialIssuer has a hostname specified and service type none", func() { + const fakeHostname = "fake.example.com" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: localhostIP, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) - it("doesn't create, then creates, then deletes the load balancer", func() { + it("starts the impersonator, generates a valid cert for the specified hostname", func() { startInformersAndController() - - // Should have started in "enabled" mode with an "endpoint", so no load balancer is needed. r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 3) requireNodesListed(kubeAPIClient.Actions()[0]) - ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) // created immediately because "endpoint" was specified + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - requireTLSServerIsRunning(ca, testServerAddr(), nil) - requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Secrets()) - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - - // Switch to "enabled" mode without an "endpoint", so a load balancer is needed now. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + when("the CredentialIssuer has a endpoint which is an IP address with a port", func() { + const fakeIPWithPort = "127.0.0.1:3000" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeIPWithPort, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, - }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + it("starts the impersonator, generates a valid cert for the specified IP address", func() { + startInformersAndController() r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 5) - requireLoadBalancerWasCreated(kubeAPIClient.Actions()[3]) - requireTLSSecretWasDeleted(kubeAPIClient.Actions()[4]) // the Secret was deleted because it contained a cert with the wrong IP - requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) - requireSigningCertProviderIsEmpty() + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeIPWithPort. + requireTLSServerIsRunning(ca, fakeIPWithPort, map[string]string{fakeIPWithPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeIPWithPort, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[3], kubeInformers.Core().V1().Services()) - deleteSecretFromTracker(tlsSecretName, kubeInformerClient) - waitForObjectToBeDeletedFromInformer(tlsSecretName, kubeInformers.Core().V1().Secrets()) + when("the CredentialIssuer has a endpoint which is a hostname with a port, service type none", func() { + const fakeHostnameWithPort = "fake.example.com:3000" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostnameWithPort, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) - // The controller should be waiting for the load balancer's ingress to become available. + it("starts the impersonator, generates a valid cert for the specified hostname", func() { + startInformersAndController() r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 5) // no new actions while it is waiting for the load balancer's ingress - requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) - requireSigningCertProviderIsEmpty() + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostnameWithPort. + requireTLSServerIsRunning(ca, fakeHostnameWithPort, map[string]string{fakeHostnameWithPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostnameWithPort, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) - // Update the ingress of the LB in the informer's client and run Sync again. - fakeIP := "127.0.0.123" - updateLoadBalancerServiceInInformerAndWait(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: fakeIP}}, kubeInformers.Core().V1().Services()) + when("switching the CredentialIssuer from ip address endpoint to hostname endpoint and back to ip address", func() { + const fakeHostname = "fake.example.com" + const fakeIP = "127.0.0.42" + + var hostnameConfig = v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + } + var ipAddressConfig = v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + } + + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, ipAddressConfig, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("regenerates the cert for the hostname, then regenerates it for the IP again", func() { + startInformersAndController() r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 6) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[5], ca) // reuses the existing CA + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) // Check that the server is running and that TLS certs that are being served are are for fakeIP. requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()}) requireCredentialIssuer(newSuccessStrategy(fakeIP, ca)) requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[5], kubeInformers.Core().V1().Secrets()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Secrets()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - // Now switch back to having the "endpoint" specified, so the load balancer is not needed anymore. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: localhostIP, - }, - }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + // Switch the endpoint config to a hostname. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, hostnameConfig, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) r.NoError(runControllerSync()) - r.Len(kubeAPIClient.Actions(), 9) - requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[6]) - requireTLSSecretWasDeleted(kubeAPIClient.Actions()[7]) - requireTLSSecretWasCreated(kubeAPIClient.Actions()[8], ca) // recreated because the endpoint was updated, reused the old CA - requireTLSServerIsRunning(ca, testServerAddr(), nil) - requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + r.Len(kubeAPIClient.Actions(), 5) + requireTLSSecretWasDeleted(kubeAPIClient.Actions()[3]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[4], ca) // reuses the old CA + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + deleteSecretFromTracker(tlsSecretName, kubeInformerClient) + waitForObjectToBeDeletedFromInformer(tlsSecretName, kubeInformers.Core().V1().Secrets()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[4], kubeInformers.Core().V1().Secrets()) + + // Switch the endpoint config back to an IP. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, ipAddressConfig, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 7) + requireTLSSecretWasDeleted(kubeAPIClient.Actions()[5]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[6], ca) // reuses the old CA again + // Check that the server is running and that TLS certs that are being served are are for fakeIP. + requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeIP, ca)) requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) }) }) + + when("the TLS cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { + const fakeHostname = "fake.example.com" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + startInformersAndController() + }) + + it("uses the existing CA cert the make a new TLS cert", func() { + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Secrets()) + + // Delete the TLS Secret that was just created from the Kube API server. Note that we never + // simulated it getting added to the informer cache, so we don't need to remove it from there. + deleteSecretFromTracker(tlsSecretName, kubeAPIClient) + + // Run again. It should create a new TLS cert using the old CA cert. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + + when("the CA cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { + const fakeHostname = "fake.example.com" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + startInformersAndController() + }) + + it("makes a new CA cert, deletes the old TLS cert, and makes a new TLS cert using the new CA", func() { + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + + // Delete the CA Secret that was just created from the Kube API server. Note that we never + // simulated it getting added to the informer cache, so we don't need to remove it from there. + deleteSecretFromTracker(caSecretName, kubeAPIClient) + + // Run again. It should create both a new CA cert and a new TLS cert using the new CA cert. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 6) + ca = requireCASecretWasCreated(kubeAPIClient.Actions()[3]) + requireTLSSecretWasDeleted(kubeAPIClient.Actions()[4]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[5], ca) // created using the new CA + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + + when("the CA cert is overwritten by another valid CA cert", func() { + const fakeHostname = "fake.example.com" + var caCrt []byte + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + + // Simulate someone updating the CA Secret out of band, e.g. when a human edits it with kubectl. + // Delete the CA Secret that was just created from the Kube API server. Note that we never + // simulated it getting added to the informer cache, so we don't need to remove it from there. + // Then add a new one. Delete + new = update, since only the final state is observed. + deleteSecretFromTracker(caSecretName, kubeAPIClient) + anotherCA := newCA() + newCASecret := newActualCASecret(anotherCA, caSecretName) + caCrt = newCASecret.Data["ca.crt"] + addSecretToTrackers(newCASecret, kubeAPIClient) + addObjectToKubeInformerAndWait(newCASecret, kubeInformers.Core().V1().Secrets()) + }) + + it("deletes the old TLS cert and makes a new TLS cert using the new CA", func() { + // Run again. It should use the updated CA cert to create a new TLS cert. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 5) + requireTLSSecretWasDeleted(kubeAPIClient.Actions()[3]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[4], caCrt) // created using the updated CA + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(caCrt, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, caCrt)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + + when("deleting the TLS cert due to mismatched CA results in an error", func() { + it.Before(func() { + kubeAPIClient.PrependReactor("delete", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { + if action.(coretesting.DeleteAction).GetName() == tlsSecretName { + return true, nil, fmt.Errorf("error on tls secret delete") + } + return false, nil, nil + }) + }) + + it("returns an error", func() { + r.Error(runControllerSync(), "error on tls secret delete") + r.Len(kubeAPIClient.Actions(), 4) + requireTLSSecretWasDeleted(kubeAPIClient.Actions()[3]) // tried to delete cert but failed + requireCredentialIssuer(newErrorStrategy("error on tls secret delete")) + requireSigningCertProviderIsEmpty() + }) + }) + }) + }) + + when("the configuration switches from enabled to disabled mode", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator and loadbalancer, then shuts it down, then starts it again", func() { + startInformersAndController() + + r.NoError(runControllerSync()) + requireTLSServerIsRunningWithoutCerts() + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + + // Update the CredentialIssuer. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + requireTLSServerIsNoLongerRunning() + r.Len(kubeAPIClient.Actions(), 4) + requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[3]) + requireCredentialIssuer(newManuallyDisabledStrategy()) + requireSigningCertProviderIsEmpty() + + deleteServiceFromTracker(loadBalancerServiceName, kubeInformerClient) + waitForObjectToBeDeletedFromInformer(loadBalancerServiceName, kubeInformers.Core().V1().Services()) + + // Update the CredentialIssuer again. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + requireTLSServerIsRunningWithoutCerts() + r.Len(kubeAPIClient.Actions(), 5) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[4]) + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + }) + }) + + when("the endpoint and mode switch from specified with no service, to not specified, to specified again", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("doesn't create, then creates, then deletes the load balancer", func() { + startInformersAndController() + + // Should have started in "enabled" mode with an "endpoint", so no load balancer is needed. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) // created immediately because "endpoint" was specified + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Secrets()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + + // Switch to "enabled" mode without an "endpoint", so a load balancer is needed now. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 5) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[3]) + requireTLSSecretWasDeleted(kubeAPIClient.Actions()[4]) // the Secret was deleted because it contained a cert with the wrong IP + requireTLSServerIsRunningWithoutCerts() + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[3], kubeInformers.Core().V1().Services()) + deleteSecretFromTracker(tlsSecretName, kubeInformerClient) + waitForObjectToBeDeletedFromInformer(tlsSecretName, kubeInformers.Core().V1().Secrets()) + + // The controller should be waiting for the load balancer's ingress to become available. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 5) // no new actions while it is waiting for the load balancer's ingress + requireTLSServerIsRunningWithoutCerts() + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + + // Update the ingress of the LB in the informer's client and run Sync again. + fakeIP := "127.0.0.123" + updateLoadBalancerServiceInInformerAndWait(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: fakeIP}}, kubeInformers.Core().V1().Services()) + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 6) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[5], ca) // reuses the existing CA + // Check that the server is running and that TLS certs that are being served are are for fakeIP. + requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[5], kubeInformers.Core().V1().Secrets()) + + // Now switch back to having the "endpoint" specified and explicitly saying that we don't want the load balancer service. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 9) + requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[6]) + requireTLSSecretWasDeleted(kubeAPIClient.Actions()[7]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[8], ca) // recreated because the endpoint was updated, reused the old CA + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) }) when("sync is called more than once", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) }) it("only starts the impersonator once and only lists the cluster's nodes once", func() { @@ -2028,6 +2095,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("getting the control plane nodes returns an error, e.g. when there are no nodes", func() { it("returns an error", func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) startInformersAndController() r.EqualError(runControllerSync(), "no nodes found") requireCredentialIssuer(newErrorStrategy("no nodes found")) @@ -2040,6 +2112,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addNodeWithRoleToTracker("worker", kubeAPIClient) impersonatorFuncReturnedFuncError = errors.New("some immediate impersonator startup error") + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) }) it("causes an immediate resync, returns an error on that next sync, and then restarts the server in a following sync", func() { @@ -2095,6 +2172,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the impersonator server dies for no apparent reason after running for a while", func() { it.Before(func() { addNodeWithRoleToTracker("worker", kubeAPIClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) }) it("causes an immediate resync, returns an error on that next sync, and then restarts the server in a following sync", func() { @@ -2169,6 +2251,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { kubeAPIClient.PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { return true, nil, fmt.Errorf("error on create") }) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) }) it("returns an error", func() { @@ -2186,6 +2273,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: "example.com", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, pinnipedInformerClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) @@ -2217,6 +2307,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: "example.com", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, pinnipedInformerClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) @@ -2248,6 +2341,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: "example.com", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, pinnipedInformerClient) addSecretToTrackers(newEmptySecret(caSecretName), kubeAPIClient, kubeInformerClient) @@ -2319,6 +2415,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) @@ -2438,6 +2537,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addSecretToTrackers(tlsSecret, kubeAPIClient, kubeInformerClient) addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeInformerClient) addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeAPIClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) }) it("deletes the invalid certs, creates new certs, and starts the impersonator", func() { @@ -2509,6 +2613,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, pinnipedInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) From 4a785e73e6437efb229ac08da7192eb2ec906ef8 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Tue, 18 May 2021 14:54:04 -0500 Subject: [PATCH 08/43] WIP fixing impersonatorconfig tests --- .../impersonator_config_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 057969ed..ebe45038 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -2073,6 +2073,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) r.NoError(pinnipedAPIClient.Tracker().Add(&v1alpha1.CredentialIssuer{ ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, Status: v1alpha1.CredentialIssuerStatus{Strategies: []v1alpha1.CredentialIssuerStrategy{preExistingStrategy}}, @@ -2367,6 +2372,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) addSecretToTrackers(newEmptySecret(tlsSecretName), kubeAPIClient, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) startInformersAndController() kubeAPIClient.PrependReactor("delete", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { return true, nil, fmt.Errorf("error on delete") @@ -2582,6 +2592,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, pinnipedInformerClient) pinnipedAPIClient.PrependReactor("create", "credentialissuers", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { return true, nil, fmt.Errorf("error on create") }) @@ -2696,7 +2711,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) }) - }, spec.Parallel(), spec.Report(report.Terminal{})) + }, spec.Report(report.Terminal{})) } type testQueue struct { From eaea3471ec7d69d4aa536c42b2c08811b37034f9 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Tue, 18 May 2021 13:50:52 -0700 Subject: [PATCH 09/43] Validation for service type none and external endpoint none Also added a few more test cases for provisioning a load balancer --- .../impersonatorconfig/impersonator_config.go | 9 +- .../impersonator_config_test.go | 82 +++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 41337f90..1d29fdd1 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -656,8 +656,7 @@ func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1a // - you have a loadbalancer AND an external endpoint -> either should work since they should be the same // - external endpoint no loadbalancer or other service -> use the endpoint config // - external endpoint and ClusterIP -> use external endpoint? - // - // - is it legal to have a clusterip and no external endpoint??? + // - clusterip and no external endpoint if config.ExternalEndpoint != "" { return c.findTLSCertificateNameFromEndpointConfig(config), nil } else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { @@ -874,7 +873,11 @@ func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, conf func validateCredentialIssuerSpec(credIssuer *v1alpha1.CredentialIssuer) error { // TODO check external endpoint for valid ip or hostname - // TODO if service type is none and externalendpoint is "" return error + impersonationProxySpec := credIssuer.Spec.ImpersonationProxy + if impersonationProxySpec.Mode != v1alpha1.ImpersonationProxyModeDisabled && + impersonationProxySpec.ExternalEndpoint == "" && impersonationProxySpec.Service.Type == v1alpha1.ImpersonationProxyServiceTypeNone { + return fmt.Errorf("invalid impersonation proxy configuration: must specify an external endpoint or set a service type") + } switch mode := credIssuer.Spec.ImpersonationProxy.Mode; mode { case v1alpha1.ImpersonationProxyModeAuto: case v1alpha1.ImpersonationProxyModeDisabled: diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index ebe45038..08acd916 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -1498,6 +1498,36 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("the CredentialIssuer has a hostname specified and service type loadbalancer", func() { + const fakeHostname = "fake.example.com" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator, generates a valid cert for the specified hostname, starts a loadbalancer", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + when("the CredentialIssuer has a endpoint which is an IP address with a port", func() { const fakeIPWithPort = "127.0.0.1:3000" it.Before(func() { @@ -1556,6 +1586,36 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("the CredentialIssuer has a endpoint which is a hostname with a port, service type loadbalancer", func() { + const fakeHostnameWithPort = "fake.example.com:3000" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostnameWithPort, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator, starts the loadbalancer, generates a valid cert for the specified hostname", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostnameWithPort. + requireTLSServerIsRunning(ca, fakeHostnameWithPort, map[string]string{fakeHostnameWithPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostnameWithPort, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + when("switching the CredentialIssuer from ip address endpoint to hostname endpoint and back to ip address", func() { const fakeHostname = "fake.example.com" const fakeIP = "127.0.0.42" @@ -2711,6 +2771,28 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) }) + + when("the impersonator is enabled but the service type is none and the external endpoint is empty", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("control-plane", kubeAPIClient) + }) + + it("returns a validation error", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "invalid impersonator configuration: invalid impersonation proxy configuration: must specify an external endpoint or set a service type") + r.Len(kubeAPIClient.Actions(), 0) + }) + }) }, spec.Report(report.Terminal{})) } From 94c370ac856b728902d4c88ed80ce458520063ff Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Tue, 18 May 2021 16:54:59 -0700 Subject: [PATCH 10/43] Annotations for impersonation load balancer --- .../impersonatorconfig/impersonator_config.go | 55 +++++-- .../impersonator_config_test.go | 148 +++++++++++++++--- .../concierge_impersonation_proxy_test.go | 10 ++ 3 files changed, 175 insertions(+), 38 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 1d29fdd1..beb466e1 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -11,6 +11,7 @@ import ( "encoding/pem" "fmt" "net" + "reflect" "strings" "time" @@ -222,7 +223,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v } if c.shouldHaveLoadBalancer(config) { - if err = c.ensureLoadBalancerIsStarted(ctx); err != nil { + if err = c.ensureLoadBalancerIsStarted(ctx, config); err != nil { return nil, err } } else { @@ -321,6 +322,18 @@ func (c *impersonatorConfigController) loadBalancerExists() (bool, error) { return true, nil } +func (c *impersonatorConfigController) loadBalancerNeedsUpdate(config *v1alpha1.ImpersonationProxySpec) (bool, error) { + lb, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName) + if err != nil { + return false, err + } + if !reflect.DeepEqual(lb.Annotations, config.Service.Annotations) { + return true, nil + } + // TODO also check for loadBalancerIP + return false, nil +} + func (c *impersonatorConfigController) tlsSecretExists() (bool, *v1.Secret, error) { secret, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) @@ -406,14 +419,7 @@ func (c *impersonatorConfigController) ensureImpersonatorIsStopped(shouldCloseEr return stopErr } -func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.Context) error { - running, err := c.loadBalancerExists() - if err != nil { - return err - } - if running { - return nil - } +func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.Context, config *v1alpha1.ImpersonationProxySpec) error { appNameLabel := c.labels[appLabelKey] loadBalancer := v1.Service{ Spec: v1.ServiceSpec{ @@ -425,17 +431,34 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.C Protocol: v1.ProtocolTCP, }, }, - Selector: map[string]string{appLabelKey: appNameLabel}, + LoadBalancerIP: config.Service.LoadBalancerIP, + Selector: map[string]string{appLabelKey: appNameLabel}, }, ObjectMeta: metav1.ObjectMeta{ - Name: c.generatedLoadBalancerServiceName, - Namespace: c.namespace, - Labels: c.labels, - Annotations: map[string]string{ - "service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "4000", // AWS' default is to time out after 60 seconds idle. Prevent that. - }, + Name: c.generatedLoadBalancerServiceName, + Namespace: c.namespace, + Labels: c.labels, + Annotations: config.Service.Annotations, }, } + running, err := c.loadBalancerExists() + if err != nil { + return err + } + if running { + needsUpdate, err := c.loadBalancerNeedsUpdate(config) + if err != nil { + return err + } + if needsUpdate { + plog.Info("updating load balancer for impersonation proxy", + "service", c.generatedLoadBalancerServiceName, + "namespace", c.namespace) + _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &loadBalancer, metav1.UpdateOptions{}) + + } + return nil + } plog.Info("creating load balancer for impersonation proxy", "service", c.generatedLoadBalancerServiceName, "namespace", c.namespace) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 08acd916..3fa247e2 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -856,7 +856,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal([]v1alpha1.CredentialIssuerStrategy{expectedStrategy}, credentialIssuer.Status.Strategies) } - var requireLoadBalancerWasCreated = func(action coretesting.Action) { + var requireLoadBalancerWasCreated = func(action coretesting.Action) *corev1.Service { createAction, ok := action.(coretesting.CreateAction) r.True(ok, "should have been able to cast this action to CreateAction: %v", action) r.Equal("create", createAction.GetVerb()) @@ -866,7 +866,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal(corev1.ServiceTypeLoadBalancer, createdLoadBalancerService.Spec.Type) r.Equal("app-name", createdLoadBalancerService.Spec.Selector["app"]) r.Equal(labels, createdLoadBalancerService.Labels) - r.Equal(map[string]string{"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "4000"}, createdLoadBalancerService.Annotations) + return createdLoadBalancerService } var requireLoadBalancerWasDeleted = func(action coretesting.Action) { @@ -877,6 +877,19 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal("services", deleteAction.GetResource().Resource) } + var requireLoadBalancerWasUpdated = func(action coretesting.Action) *corev1.Service { + updateAction, ok := action.(coretesting.UpdateAction) + r.True(ok, "should have been able to cast this action to UpdateAction: %v", action) + r.Equal("update", updateAction.GetVerb()) + updatedLoadBalancerService := updateAction.GetObject().(*corev1.Service) + r.Equal(loadBalancerServiceName, updatedLoadBalancerService.Name) + r.Equal(installedInNamespace, updatedLoadBalancerService.Namespace) + r.Equal(corev1.ServiceTypeLoadBalancer, updatedLoadBalancerService.Spec.Type) + r.Equal("app-name", updatedLoadBalancerService.Spec.Selector["app"]) + r.Equal(labels, updatedLoadBalancerService.Labels) + return updatedLoadBalancerService + } + var requireTLSSecretWasDeleted = func(action coretesting.Action) { deleteAction, ok := action.(coretesting.DeleteAction) r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) @@ -1469,6 +1482,35 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("credentialissuer has service type loadbalancer and custom annotations", func() { + annotations := map[string]string{"some-annotation-key": "some-annotation-value"} + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + Annotations: annotations, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator, generates a valid cert for the specified hostname, starts a loadbalancer", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + lbService := requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + require.Equal(t, lbService.Annotations, annotations) + requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSServerIsRunningWithoutCerts() + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + }) + }) + when("the CredentialIssuer has a hostname specified and service type none", func() { const fakeHostname = "fake.example.com" it.Before(func() { @@ -1586,7 +1628,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the CredentialIssuer has a endpoint which is a hostname with a port, service type loadbalancer", func() { + when("the CredentialIssuer has a endpoint which is a hostname with a port, service type loadbalancer with loadbalancerip", func() { const fakeHostnameWithPort = "fake.example.com:3000" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ @@ -1594,7 +1636,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostnameWithPort, Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + LoadBalancerIP: localhostIP, }, }, }, pinnipedInformerClient) @@ -1606,7 +1649,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 4) requireNodesListed(kubeAPIClient.Actions()[0]) - requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + lbService := requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + require.Equal(t, lbService.Spec.LoadBalancerIP, localhostIP) ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) // Check that the server is running and that TLS certs that are being served are are for fakeHostnameWithPort. @@ -2011,6 +2055,64 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("requesting a load balancer via CredentialIssuer, then updating the annotations", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("creates the load balancer without annotations, then adds them", func() { + startInformersAndController() + + // Should have started in "enabled" mode with service type load balancer, so one is created. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireNodesListed(kubeAPIClient.Actions()[0]) + lbService := requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + require.Equal(t, map[string]string(nil), lbService.Annotations) // there should be no annotations at first + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[3], kubeInformers.Core().V1().Secrets()) + + // Add annotations to the spec. + annotations := map[string]string{"my-annotation-key": "my-annotation-val"} + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + Annotations: annotations, + }, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 5) // one more item to update the loadbalancer + lbService = requireLoadBalancerWasUpdated(kubeAPIClient.Actions()[4]) + require.Equal(t, annotations, lbService.Annotations) // now the annotations should exist on the load balancer + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + when("sync is called more than once", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) @@ -2772,25 +2874,27 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the impersonator is enabled but the service type is none and the external endpoint is empty", func() { - it.Before(func() { - addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: "", - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + when("CredentialIssuer spec validation", func() { + when("the impersonator is enabled but the service type is none and the external endpoint is empty", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, - }, - }, pinnipedInformerClient) - addNodeWithRoleToTracker("control-plane", kubeAPIClient) - }) + }, pinnipedInformerClient) + addNodeWithRoleToTracker("control-plane", kubeAPIClient) + }) - it("returns a validation error", func() { - startInformersAndController() - r.EqualError(runControllerSync(), "invalid impersonator configuration: invalid impersonation proxy configuration: must specify an external endpoint or set a service type") - r.Len(kubeAPIClient.Actions(), 0) + it("returns a validation error", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "invalid impersonator configuration: invalid impersonation proxy configuration: must specify an external endpoint or set a service type") + r.Len(kubeAPIClient.Actions(), 0) + }) }) }) }, spec.Report(report.Terminal{})) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index 6e7153c6..e3273cab 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -191,6 +191,16 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // this point depending on the capabilities of the cluster under test. We handle each possible case here. switch { case impersonatorShouldHaveStartedAutomaticallyByDefault && clusterSupportsLoadBalancers: + // configure the credential issuer spec to have the impersonation proxy in auto mode + updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ + ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + Mode: conciergev1alpha.ImpersonationProxyModeAuto, + Service: conciergev1alpha.ImpersonationProxyServiceSpec{ + Type: conciergev1alpha.ImpersonationProxyServiceTypeLoadBalancer, + Annotations: map[string]string{"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "4000"}, + }, + }, + }) // Auto mode should have decided that the impersonator will run and should have started a load balancer, // and we will be able to use the load balancer to access the impersonator. (e.g. GKE, AKS, EKS) // Check that load balancer has been automatically created by the impersonator's "auto" mode. From 54e0b83146b4360782f5d0a1f6251827108677a1 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 19 May 2021 11:39:28 -0500 Subject: [PATCH 11/43] Update API so that impersonationProxy spec is a pointer. --- .../v1alpha1/types_credentialissuer.go.tmpl | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl b/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl index c102c1a8..60edeccc 100644 --- a/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl +++ b/apis/concierge/config/v1alpha1/types_credentialissuer.go.tmpl @@ -45,9 +45,7 @@ const ( // CredentialIssuerSpec describes the intended configuration of the Concierge. type CredentialIssuerSpec struct { // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. - // - //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} - ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` + ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"` } // ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. @@ -88,20 +86,17 @@ type ImpersonationProxySpec struct { // - "disabled" explicitly disables the impersonation proxy. This is the default. // - "enabled" explicitly enables the impersonation proxy. // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. - // - // +kubebuilder:default:="disabled" Mode ImpersonationProxyMode `json:"mode"` - // Service describes the configuraiton + // Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. // // +kubebuilder:default:={"type": "LoadBalancer"} Service ImpersonationProxyServiceSpec `json:"service"` - // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this - // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be - // advertised. + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will + // be served using the external name of the LoadBalancer service or the cluster service DNS name. // - // Setting this field disables the automatic creation of this LoadBalancer Service. + // This field must be non-empty when spec.impersonationProxy.service.mode is "None". // // +optional ExternalEndpoint string `json:"externalEndpoint,omitempty"` @@ -228,7 +223,6 @@ type CredentialIssuer struct { // Spec describes the intended configuration of the Concierge. // // +optional - // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} Spec CredentialIssuerSpec `json:"spec"` // CredentialIssuerStatus describes the status of the Concierge. From 13372a43e6aa1cca5100f38c2c65080f65a02fad Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 19 May 2021 11:39:53 -0500 Subject: [PATCH 12/43] Update generated code from previous commit. --- ...cierge.pinniped.dev_credentialissuers.yaml | 22 +++++-------------- generated/1.17/README.adoc | 8 +++---- .../config/v1alpha1/types_credentialissuer.go | 16 +++++--------- .../config/v1alpha1/zz_generated.deepcopy.go | 6 ++++- ...cierge.pinniped.dev_credentialissuers.yaml | 22 +++++-------------- generated/1.18/README.adoc | 8 +++---- .../config/v1alpha1/types_credentialissuer.go | 16 +++++--------- .../config/v1alpha1/zz_generated.deepcopy.go | 6 ++++- ...cierge.pinniped.dev_credentialissuers.yaml | 22 +++++-------------- generated/1.19/README.adoc | 8 +++---- .../config/v1alpha1/types_credentialissuer.go | 16 +++++--------- .../config/v1alpha1/zz_generated.deepcopy.go | 6 ++++- ...cierge.pinniped.dev_credentialissuers.yaml | 22 +++++-------------- generated/1.20/README.adoc | 8 +++---- .../config/v1alpha1/types_credentialissuer.go | 16 +++++--------- .../config/v1alpha1/zz_generated.deepcopy.go | 6 ++++- ...cierge.pinniped.dev_credentialissuers.yaml | 22 +++++-------------- .../config/v1alpha1/types_credentialissuer.go | 16 +++++--------- .../config/v1alpha1/zz_generated.deepcopy.go | 6 ++++- 19 files changed, 96 insertions(+), 156 deletions(-) diff --git a/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml b/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml index 60ffaee3..7b94b098 100644 --- a/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/deploy/concierge/config.concierge.pinniped.dev_credentialissuers.yaml @@ -37,31 +37,20 @@ spec: metadata: type: object spec: - default: - impersonationProxy: - mode: disabled - service: - type: LoadBalancer description: Spec describes the intended configuration of the Concierge. properties: impersonationProxy: - default: - mode: disabled - service: - type: LoadBalancer description: ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. properties: externalEndpoint: description: "ExternalEndpoint describes the HTTPS endpoint where - the proxy will be exposed. If the proxy is enabled and this - field is not set, a Service of type LoadBalancer will be automatically - provisioned and its external name will be advertised. \n Setting - this field disables the automatic creation of this LoadBalancer - Service." + the proxy will be exposed. If not set, the proxy will be served + using the external name of the LoadBalancer service or the cluster + service DNS name. \n This field must be non-empty when spec.impersonationProxy.service.mode + is \"None\"." type: string mode: - default: disabled description: 'Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the @@ -75,7 +64,8 @@ spec: service: default: type: LoadBalancer - description: Service describes the configuraiton + description: Service describes the configuration of the Service + provisioned to expose the impersonation proxy to clients. properties: annotations: additionalProperties: diff --git a/generated/1.17/README.adoc b/generated/1.17/README.adoc index 955e4920..a356bea7 100644 --- a/generated/1.17/README.adoc +++ b/generated/1.17/README.adoc @@ -398,7 +398,7 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec"] ==== ImpersonationProxySpec -ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + .Appears In: **** @@ -409,9 +409,9 @@ ImpersonationProxySpec describes the intended configuration of the Concierge imp |=== | Field | Description | *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. -| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton -| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. - Setting this field disables the automatic creation of this LoadBalancer Service. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will be served using the external name of the LoadBalancer service or the cluster service DNS name. + This field must be non-empty when spec.impersonationProxy.service.mode is "None". |=== diff --git a/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go index c102c1a8..60edeccc 100644 --- a/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.17/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -45,9 +45,7 @@ const ( // CredentialIssuerSpec describes the intended configuration of the Concierge. type CredentialIssuerSpec struct { // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. - // - //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} - ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` + ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"` } // ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. @@ -88,20 +86,17 @@ type ImpersonationProxySpec struct { // - "disabled" explicitly disables the impersonation proxy. This is the default. // - "enabled" explicitly enables the impersonation proxy. // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. - // - // +kubebuilder:default:="disabled" Mode ImpersonationProxyMode `json:"mode"` - // Service describes the configuraiton + // Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. // // +kubebuilder:default:={"type": "LoadBalancer"} Service ImpersonationProxyServiceSpec `json:"service"` - // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this - // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be - // advertised. + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will + // be served using the external name of the LoadBalancer service or the cluster service DNS name. // - // Setting this field disables the automatic creation of this LoadBalancer Service. + // This field must be non-empty when spec.impersonationProxy.service.mode is "None". // // +optional ExternalEndpoint string `json:"externalEndpoint,omitempty"` @@ -228,7 +223,6 @@ type CredentialIssuer struct { // Spec describes the intended configuration of the Concierge. // // +optional - // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} Spec CredentialIssuerSpec `json:"spec"` // CredentialIssuerStatus describes the status of the Concierge. diff --git a/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index d5b38b36..cf679b2c 100644 --- a/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.17/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -117,7 +117,11 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { *out = *in - in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + if in.ImpersonationProxy != nil { + in, out := &in.ImpersonationProxy, &out.ImpersonationProxy + *out = new(ImpersonationProxySpec) + (*in).DeepCopyInto(*out) + } return } diff --git a/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 60ffaee3..7b94b098 100644 --- a/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.17/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -37,31 +37,20 @@ spec: metadata: type: object spec: - default: - impersonationProxy: - mode: disabled - service: - type: LoadBalancer description: Spec describes the intended configuration of the Concierge. properties: impersonationProxy: - default: - mode: disabled - service: - type: LoadBalancer description: ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. properties: externalEndpoint: description: "ExternalEndpoint describes the HTTPS endpoint where - the proxy will be exposed. If the proxy is enabled and this - field is not set, a Service of type LoadBalancer will be automatically - provisioned and its external name will be advertised. \n Setting - this field disables the automatic creation of this LoadBalancer - Service." + the proxy will be exposed. If not set, the proxy will be served + using the external name of the LoadBalancer service or the cluster + service DNS name. \n This field must be non-empty when spec.impersonationProxy.service.mode + is \"None\"." type: string mode: - default: disabled description: 'Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the @@ -75,7 +64,8 @@ spec: service: default: type: LoadBalancer - description: Service describes the configuraiton + description: Service describes the configuration of the Service + provisioned to expose the impersonation proxy to clients. properties: annotations: additionalProperties: diff --git a/generated/1.18/README.adoc b/generated/1.18/README.adoc index 6163e1ed..d98c17d8 100644 --- a/generated/1.18/README.adoc +++ b/generated/1.18/README.adoc @@ -398,7 +398,7 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyspec"] ==== ImpersonationProxySpec -ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + .Appears In: **** @@ -409,9 +409,9 @@ ImpersonationProxySpec describes the intended configuration of the Concierge imp |=== | Field | Description | *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. -| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton -| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. - Setting this field disables the automatic creation of this LoadBalancer Service. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will be served using the external name of the LoadBalancer service or the cluster service DNS name. + This field must be non-empty when spec.impersonationProxy.service.mode is "None". |=== diff --git a/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go index c102c1a8..60edeccc 100644 --- a/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.18/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -45,9 +45,7 @@ const ( // CredentialIssuerSpec describes the intended configuration of the Concierge. type CredentialIssuerSpec struct { // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. - // - //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} - ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` + ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"` } // ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. @@ -88,20 +86,17 @@ type ImpersonationProxySpec struct { // - "disabled" explicitly disables the impersonation proxy. This is the default. // - "enabled" explicitly enables the impersonation proxy. // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. - // - // +kubebuilder:default:="disabled" Mode ImpersonationProxyMode `json:"mode"` - // Service describes the configuraiton + // Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. // // +kubebuilder:default:={"type": "LoadBalancer"} Service ImpersonationProxyServiceSpec `json:"service"` - // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this - // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be - // advertised. + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will + // be served using the external name of the LoadBalancer service or the cluster service DNS name. // - // Setting this field disables the automatic creation of this LoadBalancer Service. + // This field must be non-empty when spec.impersonationProxy.service.mode is "None". // // +optional ExternalEndpoint string `json:"externalEndpoint,omitempty"` @@ -228,7 +223,6 @@ type CredentialIssuer struct { // Spec describes the intended configuration of the Concierge. // // +optional - // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} Spec CredentialIssuerSpec `json:"spec"` // CredentialIssuerStatus describes the status of the Concierge. diff --git a/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index d5b38b36..cf679b2c 100644 --- a/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.18/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -117,7 +117,11 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { *out = *in - in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + if in.ImpersonationProxy != nil { + in, out := &in.ImpersonationProxy, &out.ImpersonationProxy + *out = new(ImpersonationProxySpec) + (*in).DeepCopyInto(*out) + } return } diff --git a/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 60ffaee3..7b94b098 100644 --- a/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.18/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -37,31 +37,20 @@ spec: metadata: type: object spec: - default: - impersonationProxy: - mode: disabled - service: - type: LoadBalancer description: Spec describes the intended configuration of the Concierge. properties: impersonationProxy: - default: - mode: disabled - service: - type: LoadBalancer description: ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. properties: externalEndpoint: description: "ExternalEndpoint describes the HTTPS endpoint where - the proxy will be exposed. If the proxy is enabled and this - field is not set, a Service of type LoadBalancer will be automatically - provisioned and its external name will be advertised. \n Setting - this field disables the automatic creation of this LoadBalancer - Service." + the proxy will be exposed. If not set, the proxy will be served + using the external name of the LoadBalancer service or the cluster + service DNS name. \n This field must be non-empty when spec.impersonationProxy.service.mode + is \"None\"." type: string mode: - default: disabled description: 'Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the @@ -75,7 +64,8 @@ spec: service: default: type: LoadBalancer - description: Service describes the configuraiton + description: Service describes the configuration of the Service + provisioned to expose the impersonation proxy to clients. properties: annotations: additionalProperties: diff --git a/generated/1.19/README.adoc b/generated/1.19/README.adoc index db74e17f..4ced2c7c 100644 --- a/generated/1.19/README.adoc +++ b/generated/1.19/README.adoc @@ -398,7 +398,7 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyspec"] ==== ImpersonationProxySpec -ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + .Appears In: **** @@ -409,9 +409,9 @@ ImpersonationProxySpec describes the intended configuration of the Concierge imp |=== | Field | Description | *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. -| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton -| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. - Setting this field disables the automatic creation of this LoadBalancer Service. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will be served using the external name of the LoadBalancer service or the cluster service DNS name. + This field must be non-empty when spec.impersonationProxy.service.mode is "None". |=== diff --git a/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go index c102c1a8..60edeccc 100644 --- a/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.19/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -45,9 +45,7 @@ const ( // CredentialIssuerSpec describes the intended configuration of the Concierge. type CredentialIssuerSpec struct { // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. - // - //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} - ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` + ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"` } // ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. @@ -88,20 +86,17 @@ type ImpersonationProxySpec struct { // - "disabled" explicitly disables the impersonation proxy. This is the default. // - "enabled" explicitly enables the impersonation proxy. // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. - // - // +kubebuilder:default:="disabled" Mode ImpersonationProxyMode `json:"mode"` - // Service describes the configuraiton + // Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. // // +kubebuilder:default:={"type": "LoadBalancer"} Service ImpersonationProxyServiceSpec `json:"service"` - // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this - // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be - // advertised. + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will + // be served using the external name of the LoadBalancer service or the cluster service DNS name. // - // Setting this field disables the automatic creation of this LoadBalancer Service. + // This field must be non-empty when spec.impersonationProxy.service.mode is "None". // // +optional ExternalEndpoint string `json:"externalEndpoint,omitempty"` @@ -228,7 +223,6 @@ type CredentialIssuer struct { // Spec describes the intended configuration of the Concierge. // // +optional - // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} Spec CredentialIssuerSpec `json:"spec"` // CredentialIssuerStatus describes the status of the Concierge. diff --git a/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index d5b38b36..cf679b2c 100644 --- a/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.19/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -117,7 +117,11 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { *out = *in - in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + if in.ImpersonationProxy != nil { + in, out := &in.ImpersonationProxy, &out.ImpersonationProxy + *out = new(ImpersonationProxySpec) + (*in).DeepCopyInto(*out) + } return } diff --git a/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 60ffaee3..7b94b098 100644 --- a/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.19/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -37,31 +37,20 @@ spec: metadata: type: object spec: - default: - impersonationProxy: - mode: disabled - service: - type: LoadBalancer description: Spec describes the intended configuration of the Concierge. properties: impersonationProxy: - default: - mode: disabled - service: - type: LoadBalancer description: ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. properties: externalEndpoint: description: "ExternalEndpoint describes the HTTPS endpoint where - the proxy will be exposed. If the proxy is enabled and this - field is not set, a Service of type LoadBalancer will be automatically - provisioned and its external name will be advertised. \n Setting - this field disables the automatic creation of this LoadBalancer - Service." + the proxy will be exposed. If not set, the proxy will be served + using the external name of the LoadBalancer service or the cluster + service DNS name. \n This field must be non-empty when spec.impersonationProxy.service.mode + is \"None\"." type: string mode: - default: disabled description: 'Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the @@ -75,7 +64,8 @@ spec: service: default: type: LoadBalancer - description: Service describes the configuraiton + description: Service describes the configuration of the Service + provisioned to expose the impersonation proxy to clients. properties: annotations: additionalProperties: diff --git a/generated/1.20/README.adoc b/generated/1.20/README.adoc index 3a71b4ad..fa0f22f9 100644 --- a/generated/1.20/README.adoc +++ b/generated/1.20/README.adoc @@ -398,7 +398,7 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser [id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyspec"] ==== ImpersonationProxySpec -ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. + .Appears In: **** @@ -409,9 +409,9 @@ ImpersonationProxySpec describes the intended configuration of the Concierge imp |=== | Field | Description | *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. -| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuraiton -| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be advertised. - Setting this field disables the automatic creation of this LoadBalancer Service. +| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. +| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will be served using the external name of the LoadBalancer service or the cluster service DNS name. + This field must be non-empty when spec.impersonationProxy.service.mode is "None". |=== diff --git a/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go index c102c1a8..60edeccc 100644 --- a/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/1.20/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -45,9 +45,7 @@ const ( // CredentialIssuerSpec describes the intended configuration of the Concierge. type CredentialIssuerSpec struct { // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. - // - //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} - ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` + ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"` } // ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. @@ -88,20 +86,17 @@ type ImpersonationProxySpec struct { // - "disabled" explicitly disables the impersonation proxy. This is the default. // - "enabled" explicitly enables the impersonation proxy. // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. - // - // +kubebuilder:default:="disabled" Mode ImpersonationProxyMode `json:"mode"` - // Service describes the configuraiton + // Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. // // +kubebuilder:default:={"type": "LoadBalancer"} Service ImpersonationProxyServiceSpec `json:"service"` - // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this - // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be - // advertised. + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will + // be served using the external name of the LoadBalancer service or the cluster service DNS name. // - // Setting this field disables the automatic creation of this LoadBalancer Service. + // This field must be non-empty when spec.impersonationProxy.service.mode is "None". // // +optional ExternalEndpoint string `json:"externalEndpoint,omitempty"` @@ -228,7 +223,6 @@ type CredentialIssuer struct { // Spec describes the intended configuration of the Concierge. // // +optional - // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} Spec CredentialIssuerSpec `json:"spec"` // CredentialIssuerStatus describes the status of the Concierge. diff --git a/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index d5b38b36..cf679b2c 100644 --- a/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.20/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -117,7 +117,11 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { *out = *in - in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + if in.ImpersonationProxy != nil { + in, out := &in.ImpersonationProxy, &out.ImpersonationProxy + *out = new(ImpersonationProxySpec) + (*in).DeepCopyInto(*out) + } return } diff --git a/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml b/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml index 60ffaee3..7b94b098 100644 --- a/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml +++ b/generated/1.20/crds/config.concierge.pinniped.dev_credentialissuers.yaml @@ -37,31 +37,20 @@ spec: metadata: type: object spec: - default: - impersonationProxy: - mode: disabled - service: - type: LoadBalancer description: Spec describes the intended configuration of the Concierge. properties: impersonationProxy: - default: - mode: disabled - service: - type: LoadBalancer description: ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. properties: externalEndpoint: description: "ExternalEndpoint describes the HTTPS endpoint where - the proxy will be exposed. If the proxy is enabled and this - field is not set, a Service of type LoadBalancer will be automatically - provisioned and its external name will be advertised. \n Setting - this field disables the automatic creation of this LoadBalancer - Service." + the proxy will be exposed. If not set, the proxy will be served + using the external name of the LoadBalancer service or the cluster + service DNS name. \n This field must be non-empty when spec.impersonationProxy.service.mode + is \"None\"." type: string mode: - default: disabled description: 'Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the @@ -75,7 +64,8 @@ spec: service: default: type: LoadBalancer - description: Service describes the configuraiton + description: Service describes the configuration of the Service + provisioned to expose the impersonation proxy to clients. properties: annotations: additionalProperties: diff --git a/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go b/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go index c102c1a8..60edeccc 100644 --- a/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go +++ b/generated/latest/apis/concierge/config/v1alpha1/types_credentialissuer.go @@ -45,9 +45,7 @@ const ( // CredentialIssuerSpec describes the intended configuration of the Concierge. type CredentialIssuerSpec struct { // ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy. - // - //+kubebuilder:default:={"mode": "disabled", "service": {"type": "LoadBalancer"}} - ImpersonationProxy ImpersonationProxySpec `json:"impersonationProxy"` + ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"` } // ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy. @@ -88,20 +86,17 @@ type ImpersonationProxySpec struct { // - "disabled" explicitly disables the impersonation proxy. This is the default. // - "enabled" explicitly enables the impersonation proxy. // - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running. - // - // +kubebuilder:default:="disabled" Mode ImpersonationProxyMode `json:"mode"` - // Service describes the configuraiton + // Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients. // // +kubebuilder:default:={"type": "LoadBalancer"} Service ImpersonationProxyServiceSpec `json:"service"` - // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If the proxy is enabled and this - // field is not set, a Service of type LoadBalancer will be automatically provisioned and its external name will be - // advertised. + // ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will + // be served using the external name of the LoadBalancer service or the cluster service DNS name. // - // Setting this field disables the automatic creation of this LoadBalancer Service. + // This field must be non-empty when spec.impersonationProxy.service.mode is "None". // // +optional ExternalEndpoint string `json:"externalEndpoint,omitempty"` @@ -228,7 +223,6 @@ type CredentialIssuer struct { // Spec describes the intended configuration of the Concierge. // // +optional - // +kubebuilder:default:={"impersonationProxy": {"mode": "disabled", "service": {"type": "LoadBalancer"}}} Spec CredentialIssuerSpec `json:"spec"` // CredentialIssuerStatus describes the status of the Concierge. diff --git a/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go b/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go index d5b38b36..cf679b2c 100644 --- a/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/latest/apis/concierge/config/v1alpha1/zz_generated.deepcopy.go @@ -117,7 +117,11 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) { *out = *in - in.ImpersonationProxy.DeepCopyInto(&out.ImpersonationProxy) + if in.ImpersonationProxy != nil { + in, out := &in.ImpersonationProxy, &out.ImpersonationProxy + *out = new(ImpersonationProxySpec) + (*in).DeepCopyInto(*out) + } return } From 297a484948ab5aa5ab05e424ede69d04ad9ebe40 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 19 May 2021 11:40:32 -0500 Subject: [PATCH 13/43] Add more validation and update tests for impersonationProxy as pointer. Signed-off-by: Matt Moyer --- .../impersonatorconfig/impersonator_config.go | 108 +++++++-- .../impersonator_config_test.go | 224 ++++++++++++++---- .../concierge_impersonation_proxy_test.go | 8 +- 3 files changed, 269 insertions(+), 71 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index beb466e1..cf3939fb 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -12,6 +12,7 @@ import ( "fmt" "net" "reflect" + "strconv" "strings" "time" @@ -23,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" corev1informers "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" @@ -262,23 +264,28 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*v1alpha1.ImpersonationProxySpec, error) { credIssuer, err := c.credIssuerInformer.Lister().Get(c.credentialIssuerResourceName) - if err != nil { return nil, fmt.Errorf("failed to get %s CredentialIssuer: %w", c.credentialIssuerResourceName, err) } - credIssuer = credIssuer.DeepCopy() - err = validateCredentialIssuerSpec(credIssuer) - if err != nil { - return nil, fmt.Errorf("invalid impersonator configuration: %v", err) + // Make a copy of the spec since we got this object from informer cache. + spec := credIssuer.Spec.DeepCopy().ImpersonationProxy + if spec == nil { + return nil, fmt.Errorf("could not load CredentialIssuer: spec.impersonationProxy is nil") + } + + // Default service type to LoadBalancer (this is normally already done via CRD defaulting). + if spec.Service.Type == "" { + spec.Service.Type = v1alpha1.ImpersonationProxyServiceTypeLoadBalancer + } + + if err := validateCredentialIssuerSpec(spec); err != nil { + return nil, fmt.Errorf("could not load CredentialIssuer spec.impersonationProxy: %w", err) } plog.Info("Read impersonation proxy config", "credentialIssuer", c.credentialIssuerResourceName, ) - if credIssuer.Spec.ImpersonationProxy.Service.Type == "" { - credIssuer.Spec.ImpersonationProxy.Service.Type = v1alpha1.ImpersonationProxyServiceTypeLoadBalancer - } - return &credIssuer.Spec.ImpersonationProxy, nil + return spec, nil } func (c *impersonatorConfigController) shouldHaveImpersonator(config *v1alpha1.ImpersonationProxySpec) bool { @@ -455,7 +462,7 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.C "service", c.generatedLoadBalancerServiceName, "namespace", c.namespace) _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &loadBalancer, metav1.UpdateOptions{}) - + // TODO: make sure this error is handled if necessary } return nil } @@ -894,19 +901,80 @@ func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, conf } } -func validateCredentialIssuerSpec(credIssuer *v1alpha1.CredentialIssuer) error { - // TODO check external endpoint for valid ip or hostname - impersonationProxySpec := credIssuer.Spec.ImpersonationProxy - if impersonationProxySpec.Mode != v1alpha1.ImpersonationProxyModeDisabled && - impersonationProxySpec.ExternalEndpoint == "" && impersonationProxySpec.Service.Type == v1alpha1.ImpersonationProxyServiceTypeNone { - return fmt.Errorf("invalid impersonation proxy configuration: must specify an external endpoint or set a service type") - } - switch mode := credIssuer.Spec.ImpersonationProxy.Mode; mode { - case v1alpha1.ImpersonationProxyModeAuto: +func validateCredentialIssuerSpec(spec *v1alpha1.ImpersonationProxySpec) error { + // Validate that the mode is one of our known values. + switch spec.Mode { case v1alpha1.ImpersonationProxyModeDisabled: + case v1alpha1.ImpersonationProxyModeAuto: case v1alpha1.ImpersonationProxyModeEnabled: default: - return fmt.Errorf("invalid impersonation proxy mode %q, valid values are auto, disabled, or enabled", mode) + return fmt.Errorf("invalid proxy mode %q (expected auto, disabled, or enabled)", spec.Mode) } + + // If disabled, ignore all other fields and consider the configuration valid. + if spec.Mode == v1alpha1.ImpersonationProxyModeDisabled { + return nil + } + + // Validate that the service type is one of our known values. + switch spec.Service.Type { + case v1alpha1.ImpersonationProxyServiceTypeNone: + case v1alpha1.ImpersonationProxyServiceTypeLoadBalancer: + case v1alpha1.ImpersonationProxyServiceTypeClusterIP: + default: + return fmt.Errorf("invalid service type %q (expected None, LoadBalancer, or ClusterIP)", spec.Service.Type) + } + + // If specified, validate that the LoadBalancerIP is a valid IPv4 or IPv6 address. + if ip := spec.Service.LoadBalancerIP; ip != "" && len(validation.IsValidIP(ip)) > 0 { + return fmt.Errorf("invalid LoadBalancerIP %q", spec.Service.LoadBalancerIP) + } + + // If service is type "None", a non-empty external endpoint must be specified. + if spec.ExternalEndpoint == "" && spec.Service.Type == v1alpha1.ImpersonationProxyServiceTypeNone { + return fmt.Errorf("externalEndpoint must be set when service.type is None") + } + + if err := validateExternalEndpoint(spec.ExternalEndpoint); err != nil { + return fmt.Errorf("invalid ExternalEndpoint %q: %w", spec.ExternalEndpoint, err) + } + return nil } + +func validateExternalEndpoint(endpoint string) error { + // Empty string is valid (no external endpoint, default to service name) + if endpoint == "" { + return nil + } + + // Try parsing it both with and without an implicit port 443 at the end. + host, port, err := net.SplitHostPort(endpoint) + + // If we got an error parsing the raw input, try adding an implicit port 443. + if err != nil { + host, port, err = net.SplitHostPort(net.JoinHostPort(endpoint, "443")) + } + + // If there's still an error, fail now. + if err != nil { + return err + } + + portInt, _ := strconv.Atoi(port) + if len(validation.IsValidPortNum(portInt)) > 0 { + return fmt.Errorf("invalid port %q", port) + } + + // Check if the host part is a valid IP address. + if len(validation.IsValidIP(host)) == 0 { + return nil + } + + // Check if the host part is a valid hostname according to RFC 1123. + if len(validation.IsDNS1123Subdomain(host)) == 0 { + return nil + } + + return fmt.Errorf("host %q is not a valid hostname or IP address", host) +} diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 3fa247e2..4f84b799 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -1019,7 +1019,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, ExternalEndpoint: localhostIP, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1068,7 +1068,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -1360,14 +1360,13 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireSigningCertProviderIsEmpty() }) }) - }) when("the configuration is disabled mode", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeDisabled, }, }, pinnipedInformerClient) @@ -1392,7 +1391,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("no load balancer", func() { it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformerClient) @@ -1423,7 +1422,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("a loadbalancer already exists", func() { it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformerClient) @@ -1456,7 +1455,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var caCrt []byte it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformerClient) @@ -1486,7 +1485,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { annotations := map[string]string{"some-annotation-key": "some-annotation-value"} it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, Service: v1alpha1.ImpersonationProxyServiceSpec{ Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, @@ -1515,7 +1514,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostname = "fake.example.com" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1544,7 +1543,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostname = "fake.example.com" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1574,7 +1573,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeIPWithPort = "127.0.0.1:3000" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeIPWithPort, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1603,7 +1602,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostnameWithPort = "fake.example.com:3000" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostnameWithPort, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1632,7 +1631,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostnameWithPort = "fake.example.com:3000" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostnameWithPort, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1665,7 +1664,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeIP = "127.0.0.42" var hostnameConfig = v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1674,7 +1673,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }, } var ipAddressConfig = v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeIP, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1739,7 +1738,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostname = "fake.example.com" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1784,7 +1783,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostname = "fake.example.com" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1832,7 +1831,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var caCrt []byte it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1904,7 +1903,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformerClient) @@ -1929,7 +1928,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Update the CredentialIssuer. updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeDisabled, }, }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) @@ -1946,7 +1945,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Update the CredentialIssuer again. updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) @@ -1964,7 +1963,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: localhostIP, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -1994,7 +1993,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Switch to "enabled" mode without an "endpoint", so a load balancer is needed now. updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) @@ -2035,7 +2034,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Now switch back to having the "endpoint" specified and explicitly saying that we don't want the load balancer service. updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: localhostIP, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2059,7 +2058,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: localhostIP, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2093,7 +2092,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Add annotations to the spec. annotations := map[string]string{"my-annotation-key": "my-annotation-val"} updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: localhostIP, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2118,7 +2117,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addSecretToTrackers(signingCASecret, kubeInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2236,7 +2235,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2263,7 +2262,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("getting the control plane nodes returns an error, e.g. when there are no nodes", func() { it("returns an error", func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2280,7 +2279,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addNodeWithRoleToTracker("worker", kubeAPIClient) impersonatorFuncReturnedFuncError = errors.New("some immediate impersonator startup error") addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2340,7 +2339,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addNodeWithRoleToTracker("worker", kubeAPIClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2393,10 +2392,27 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - when("the CredentialIssuer is invalid", func() { + when("the CredentialIssuer has nil impersonation spec", func() { it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: nil, + }, pinnipedInformerClient) + }) + + it("returns an error", func() { + startInformersAndController() + errString := `could not load CredentialIssuer: spec.impersonationProxy is nil` + r.EqualError(runControllerSync(), errString) + requireCredentialIssuer(newErrorStrategy(errString)) + requireSigningCertProviderIsEmpty() + requireTLSServerWasNeverStarted() + }) + }) + + when("the CredentialIssuer has invalid mode", func() { + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: "not-valid", }, }, pinnipedInformerClient) @@ -2404,7 +2420,71 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it("returns an error", func() { startInformersAndController() - errString := `invalid impersonator configuration: invalid impersonation proxy mode "not-valid", valid values are auto, disabled, or enabled` + errString := `could not load CredentialIssuer spec.impersonationProxy: invalid proxy mode "not-valid" (expected auto, disabled, or enabled)` + r.EqualError(runControllerSync(), errString) + requireCredentialIssuer(newErrorStrategy(errString)) + requireSigningCertProviderIsEmpty() + requireTLSServerWasNeverStarted() + }) + }) + + when("the CredentialIssuer has invalid service type", func() { + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: "not-valid", + }, + }, + }, pinnipedInformerClient) + }) + + it("returns an error", func() { + startInformersAndController() + errString := `could not load CredentialIssuer spec.impersonationProxy: invalid service type "not-valid" (expected None, LoadBalancer, or ClusterIP)` + r.EqualError(runControllerSync(), errString) + requireCredentialIssuer(newErrorStrategy(errString)) + requireSigningCertProviderIsEmpty() + requireTLSServerWasNeverStarted() + }) + }) + + when("the CredentialIssuer has invalid LoadBalancerIP", func() { + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + LoadBalancerIP: "invalid-ip-address", + }, + }, + }, pinnipedInformerClient) + }) + + it("returns an error", func() { + startInformersAndController() + errString := `could not load CredentialIssuer spec.impersonationProxy: invalid LoadBalancerIP "invalid-ip-address"` + r.EqualError(runControllerSync(), errString) + requireCredentialIssuer(newErrorStrategy(errString)) + requireSigningCertProviderIsEmpty() + requireTLSServerWasNeverStarted() + }) + }) + + when("the CredentialIssuer has invalid ExternalEndpoint", func() { + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "[invalid", + }, + }, pinnipedInformerClient) + }) + + it("returns an error", func() { + startInformersAndController() + errString := `could not load CredentialIssuer spec.impersonationProxy: invalid ExternalEndpoint "[invalid": address [invalid:443: missing ']' in address` r.EqualError(runControllerSync(), errString) requireCredentialIssuer(newErrorStrategy(errString)) requireSigningCertProviderIsEmpty() @@ -2419,7 +2499,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { return true, nil, fmt.Errorf("error on create") }) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2437,7 +2517,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("there is an error creating the tls secret", func() { it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: "example.com", Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2471,7 +2551,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("there is an error creating the CA secret", func() { it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: "example.com", Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2505,7 +2585,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addNodeWithRoleToTracker("control-plane", kubeAPIClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: "example.com", Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2535,7 +2615,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) addSecretToTrackers(newEmptySecret(tlsSecretName), kubeAPIClient, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2562,7 +2642,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addNodeWithRoleToTracker("control-plane", kubeAPIClient) addSecretToTrackers(newEmptySecret(tlsSecretName), kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeDisabled, }, }, pinnipedInformerClient) @@ -2584,7 +2664,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: localhostIP, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2647,7 +2727,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, }, pinnipedInformerClient) @@ -2710,7 +2790,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeInformerClient) addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeAPIClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2755,7 +2835,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addSecretToTrackers(signingCASecret, kubeInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeAuto, }, }, pinnipedInformerClient) @@ -2787,7 +2867,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostname = "foo.example.com" it.Before(func() { addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: fakeHostname, Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2879,7 +2959,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, ExternalEndpoint: "", Service: v1alpha1.ImpersonationProxyServiceSpec{ @@ -2892,7 +2972,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it("returns a validation error", func() { startInformersAndController() - r.EqualError(runControllerSync(), "invalid impersonator configuration: invalid impersonation proxy configuration: must specify an external endpoint or set a service type") + r.EqualError(runControllerSync(), "could not load CredentialIssuer spec.impersonationProxy: externalEndpoint must be set when service.type is None") r.Len(kubeAPIClient.Actions(), 0) }) }) @@ -2921,3 +3001,53 @@ func (q *testQueue) AddRateLimited(key controllerlib.Key) { q.key = key } + +func TestValidateExternalEndpoint(t *testing.T) { + t.Parallel() + + for _, tt := range []struct { + input string + expectErr string + }{ + {input: ""}, + {input: "127.0.0.1"}, + {input: "127.0.0.1:8443"}, + {input: "[127.0.0.1]:8443"}, + {input: "2001:db8::ffff"}, + {input: "[2001:db8::ffff]:8443"}, + {input: "host.example.com"}, + {input: "host-dev.example.com"}, + {input: "host.example.com:8443"}, + {input: "[host.example.com]:8443"}, + { + input: "https://host.example.com", + expectErr: `invalid port "//host.example.com"`, + }, + { + input: "host.example.com/some/path", + expectErr: `host "host.example.com/some/path" is not a valid hostname or IP address`, + }, + { + input: "[host.example.com", + expectErr: "address [host.example.com:443: missing ']' in address", + }, + { + input: "___.example.com:1234", + expectErr: `host "___.example.com" is not a valid hostname or IP address`, + }, + { + input: "HOST.EXAMPLE.COM", + expectErr: `host "HOST.EXAMPLE.COM" is not a valid hostname or IP address`, + }, + } { + tt := tt + t.Run(fmt.Sprintf("parse %q", tt.input), func(t *testing.T) { + got := validateExternalEndpoint(tt.input) + if tt.expectErr == "" { + require.NoError(t, got) + } else { + require.EqualError(t, got, tt.expectErr) + } + }) + } +} diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index e3273cab..ff5ebd9e 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -193,7 +193,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl case impersonatorShouldHaveStartedAutomaticallyByDefault && clusterSupportsLoadBalancers: // configure the credential issuer spec to have the impersonation proxy in auto mode updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ - ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ Mode: conciergev1alpha.ImpersonationProxyModeAuto, Service: conciergev1alpha.ImpersonationProxyServiceSpec{ Type: conciergev1alpha.ImpersonationProxyServiceTypeLoadBalancer, @@ -223,7 +223,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // Create configuration to make the impersonation proxy turn on with no endpoint (i.e. automatically create a load balancer). updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ - ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ Mode: conciergev1alpha.ImpersonationProxyModeEnabled, }, }) @@ -249,7 +249,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // Create configuration to make the impersonation proxy turn on with a hard coded endpoint (without a load balancer). updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ - ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ Mode: conciergev1alpha.ImpersonationProxyModeEnabled, ExternalEndpoint: proxyServiceEndpoint, }, @@ -1185,7 +1185,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl t.Run("manually disabling the impersonation proxy feature", func(t *testing.T) { // Update configuration to force the proxy to disabled mode updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ - ImpersonationProxy: conciergev1alpha.ImpersonationProxySpec{ + ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ Mode: conciergev1alpha.ImpersonationProxyModeDisabled, }, }) From 0b66321902fd5307e0a826a4a9c0e27571f66ab1 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Wed, 19 May 2021 11:05:35 -0700 Subject: [PATCH 14/43] Changes to make the linter pass --- .../impersonatorconfig/impersonator_config.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index cf3939fb..03ed6d24 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -462,7 +462,7 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.C "service", c.generatedLoadBalancerServiceName, "namespace", c.namespace) _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &loadBalancer, metav1.UpdateOptions{}) - // TODO: make sure this error is handled if necessary + return err } return nil } @@ -689,10 +689,10 @@ func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1a // - clusterip and no external endpoint if config.ExternalEndpoint != "" { return c.findTLSCertificateNameFromEndpointConfig(config), nil - } else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { - // c.findTLSCertificateNameFromClusterIPService() - // TODO implement this - } + } // else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { + // // c.findTLSCertificateNameFromClusterIPService() + // // TODO implement this + //} return c.findTLSCertificateNameFromLoadBalancer() } From 3bb95f1de2b2a85e0b22ba69c36e4bed616c3590 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Wed, 19 May 2021 11:56:54 -0700 Subject: [PATCH 15/43] Give kubeclient_test some default values for credentialissuer spec --- test/integration/kubeclient_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/integration/kubeclient_test.go b/test/integration/kubeclient_test.go index 88edc854..9d57bd08 100644 --- a/test/integration/kubeclient_test.go +++ b/test/integration/kubeclient_test.go @@ -228,6 +228,11 @@ func TestKubeClientOwnerRef(t *testing.T) { GenerateName: "owner-ref-test-", OwnerReferences: nil, // no owner refs set }, + Spec: conciergeconfigv1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &conciergeconfigv1alpha1.ImpersonationProxySpec{ + Mode: conciergeconfigv1alpha1.ImpersonationProxyModeDisabled, + }, + }, }, metav1.CreateOptions{}, ) From 9e61640c924c407bf01d38aaae8c0af843196a2c Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Wed, 19 May 2021 14:16:15 -0700 Subject: [PATCH 16/43] LoadBalancerIP updated dynamically --- .../impersonatorconfig/impersonator_config.go | 5 +- .../impersonator_config_test.go | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 03ed6d24..4719ca35 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -334,10 +334,13 @@ func (c *impersonatorConfigController) loadBalancerNeedsUpdate(config *v1alpha1. if err != nil { return false, err } + // TODO this will break if anything other than pinniped is adding annotations if !reflect.DeepEqual(lb.Annotations, config.Service.Annotations) { return true, nil } - // TODO also check for loadBalancerIP + if lb.Spec.LoadBalancerIP != config.Service.LoadBalancerIP { + return true, nil + } return false, nil } diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 4f84b799..9bd33ca7 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -2112,6 +2112,65 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("requesting a load balancer via CredentialIssuer, then adding a static loadBalancerIP to the spec", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("creates the load balancer without loadBalancerIP set, then adds it", func() { + startInformersAndController() + + // Should have started in "enabled" mode with service type load balancer, so one is created. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireNodesListed(kubeAPIClient.Actions()[0]) + lbService := requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + require.Equal(t, map[string]string(nil), lbService.Annotations) // there should be no annotations at first + require.Equal(t, "", lbService.Spec.LoadBalancerIP) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[3], kubeInformers.Core().V1().Secrets()) + + // Add annotations to the spec. + loadBalancerIP := "1.2.3.4" + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + LoadBalancerIP: loadBalancerIP, + }, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 5) // one more item to update the loadbalancer + lbService = requireLoadBalancerWasUpdated(kubeAPIClient.Actions()[4]) + require.Equal(t, loadBalancerIP, lbService.Spec.LoadBalancerIP) + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + when("sync is called more than once", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) From 63c39454f6717c00a3b5204035c2ebde187b0a36 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Wed, 19 May 2021 17:00:28 -0700 Subject: [PATCH 17/43] WIP on impersonation clusterip service --- .../impersonatorconfig/impersonator_config.go | 63 +++++++++++++++++++ .../impersonator_config_test.go | 42 +++++++++++++ .../controllermanager/prepare_controllers.go | 1 + 3 files changed, 106 insertions(+) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 4719ca35..6c93b202 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -57,6 +57,7 @@ type impersonatorConfigController struct { namespace string credentialIssuerResourceName string generatedLoadBalancerServiceName string + generatedClusterIPServiceName string tlsSecretName string caSecretName string impersonationSignerSecretName string @@ -90,6 +91,7 @@ func NewImpersonatorConfigController( withInformer pinnipedcontroller.WithInformerOptionFunc, withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc, generatedLoadBalancerServiceName string, + generatedClusterIPServiceName string, tlsSecretName string, caSecretName string, labels map[string]string, @@ -106,6 +108,7 @@ func NewImpersonatorConfigController( namespace: namespace, credentialIssuerResourceName: credentialIssuerResourceName, generatedLoadBalancerServiceName: generatedLoadBalancerServiceName, + generatedClusterIPServiceName: generatedClusterIPServiceName, tlsSecretName: tlsSecretName, caSecretName: caSecretName, impersonationSignerSecretName: impersonationSignerSecretName, @@ -234,6 +237,16 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v } } + if c.shouldHaveClusterIPService(config) { + if err = c.ensureClusterIPServiceIsStarted(ctx, config); err != nil { + return nil, err + } + } // else { // TODO test stopping the cluster ip service + // if err = c.ensureClusterIPServiceIsStopped(ctx); err != nil { + // return nil, err + // } + //} + nameInfo, err := c.findDesiredTLSCertificateName(config) if err != nil { // Unexpected error while determining the name that should go into the certs, so clear any existing certs. @@ -308,6 +321,10 @@ func (c *impersonatorConfigController) shouldHaveLoadBalancer(config *v1alpha1.I return c.shouldHaveImpersonator(config) && config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeLoadBalancer } +func (c *impersonatorConfigController) shouldHaveClusterIPService(config *v1alpha1.ImpersonationProxySpec) bool { + return c.shouldHaveImpersonator(config) && config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP +} + func (c *impersonatorConfigController) shouldHaveTLSSecret(config *v1alpha1.ImpersonationProxySpec) bool { return c.shouldHaveImpersonator(config) } @@ -491,6 +508,52 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.C return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{}) } +func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx context.Context, config *v1alpha1.ImpersonationProxySpec) error { + appNameLabel := c.labels[appLabelKey] + clusterIP := v1.Service{ + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeClusterIP, + Ports: []v1.ServicePort{ + { + TargetPort: intstr.FromInt(impersonationProxyPort), + Port: defaultHTTPSPort, + Protocol: v1.ProtocolTCP, + }, + }, + Selector: map[string]string{appLabelKey: appNameLabel}, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.generatedClusterIPServiceName, + Namespace: c.namespace, + Labels: c.labels, + Annotations: config.Service.Annotations, + }, + } + //running, err := c.ClusterIPExists() // TODO test that clusterip is only created once + //if err != nil { + // return err + //} + //if running { + // needsUpdate, err := c.ClusterIPNeedsUpdate(config) // TODO test updating annotations on clusterip + // if err != nil { + // return err + // } + // if needsUpdate { + // plog.Info("updating load balancer for impersonation proxy", + // "service", c.generatedLoadBalancerServiceName, + // "namespace", c.namespace) + // _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &loadBalancer, metav1.UpdateOptions{}) + // return err + // } + // return nil + //} + plog.Info("creating cluster ip for impersonation proxy", + "service", c.generatedClusterIPServiceName, + "namespace", c.namespace) + _, err := c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, &clusterIP, metav1.CreateOptions{}) + return err +} + func (c *impersonatorConfigController) ensureTLSSecret(ctx context.Context, nameInfo *certNameInfo, ca *certauthority.CA) error { secretFromInformer, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 9bd33ca7..87ac1495 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -49,6 +49,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { const installedInNamespace = "some-namespace" const credentialIssuerResourceName = "some-credential-issuer-resource-name" const generatedLoadBalancerServiceName = "some-service-resource-name" + const generatedClusterIPServiceName = "some-cluster-ip-resource-name" const tlsSecretName = "some-tls-secret-name" //nolint:gosec // this is not a credential const caSecretName = "some-ca-secret-name" const caSignerName = "some-ca-signer-name" @@ -81,6 +82,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { observableWithInformerOption.WithInformer, observableWithInitialEventOption.WithInitialEvent, generatedLoadBalancerServiceName, + generatedClusterIPServiceName, tlsSecretName, caSecretName, nil, @@ -251,6 +253,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const installedInNamespace = "some-namespace" const credentialIssuerResourceName = "some-credential-issuer-resource-name" const loadBalancerServiceName = "some-service-resource-name" + const clusterIPServiceName = "some-cluster-ip-resource-name" const tlsSecretName = "some-tls-secret-name" //nolint:gosec // this is not a credential const caSecretName = "some-ca-secret-name" const caSignerName = "some-ca-signer-name" @@ -525,6 +528,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { controllerlib.WithInformer, controllerlib.WithInitialEvent, loadBalancerServiceName, + clusterIPServiceName, tlsSecretName, caSecretName, labels, @@ -890,6 +894,17 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { return updatedLoadBalancerService } + var requireClusterIPWasCreated = func(action coretesting.Action) { + createAction, ok := action.(coretesting.CreateAction) + r.True(ok, "should have been able to cast this action to CreateAction: %v", action) + r.Equal("create", createAction.GetVerb()) + createdClusterIPService := createAction.GetObject().(*corev1.Service) + r.Equal(clusterIPServiceName, createdClusterIPService.Name) + r.Equal(corev1.ServiceTypeClusterIP, createdClusterIPService.Spec.Type) + r.Equal("app-name", createdClusterIPService.Spec.Selector["app"]) + r.Equal(labels, createdClusterIPService.Labels) + } + var requireTLSSecretWasDeleted = func(action coretesting.Action) { deleteAction, ok := action.(coretesting.DeleteAction) r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) @@ -1569,6 +1584,33 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("the CredentialIssuer has a hostname specified and service type clusterip", func() { + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator and creates a clusterip service", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireClusterIPWasCreated(kubeAPIClient.Actions()[1]) + requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + // Check that the server is running without certs. + requireTLSServerIsRunningWithoutCerts() + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + }) + }) + when("the CredentialIssuer has a endpoint which is an IP address with a port", func() { const fakeIPWithPort = "127.0.0.1:3000" it.Before(func() { diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index 3aa75c39..5b27dd83 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -260,6 +260,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { controllerlib.WithInformer, controllerlib.WithInitialEvent, c.NamesConfig.ImpersonationLoadBalancerService, + "impersonation-proxy-cluster-ip", // TODO wire this through from namesConfig c.NamesConfig.ImpersonationTLSCertificateSecret, c.NamesConfig.ImpersonationCACertificateSecret, c.Labels, From ec25259901739bc98c6ecc6dd71ef4008328b5b3 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 20 May 2021 12:26:07 -0500 Subject: [PATCH 18/43] Update impersonatorconfig controller to use new CredentialIssuer update helper. Signed-off-by: Margo Crawford --- .../impersonatorconfig/impersonator_config.go | 53 +- .../impersonator_config_test.go | 631 +++++++++++------- ...eate_or_update_credential_issuer_config.go | 85 --- ...or_update_credential_issuer_config_test.go | 301 --------- internal/controller/issuerconfig/doc.go | 5 - .../{update_strategy.go => issuerconfig.go} | 18 +- ..._strategy_test.go => issuerconfig_test.go} | 0 7 files changed, 403 insertions(+), 690 deletions(-) delete mode 100644 internal/controller/issuerconfig/create_or_update_credential_issuer_config.go delete mode 100644 internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go delete mode 100644 internal/controller/issuerconfig/doc.go rename internal/controller/issuerconfig/{update_strategy.go => issuerconfig.go} (82%) rename internal/controller/issuerconfig/{update_strategy_test.go => issuerconfig_test.go} (100%) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 6c93b202..3fdbed78 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -21,6 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/errors" + utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/intstr" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -152,8 +153,13 @@ func NewImpersonatorConfigController( func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error { plog.Debug("Starting impersonatorConfigController Sync") - strategy, err := c.doSync(syncCtx) + // Load the CredentialIssuer that we'll update with status. + credIssuer, err := c.credIssuerInformer.Lister().Get(c.credentialIssuerResourceName) + if err != nil { + return fmt.Errorf("could not get CredentialIssuer to update: %w", err) + } + strategy, err := c.doSync(syncCtx, credIssuer) if err != nil { strategy = &v1alpha1.CredentialIssuerStrategy{ Type: v1alpha1.ImpersonationProxyStrategyType, @@ -166,13 +172,12 @@ func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error c.clearSignerCA() } - updateStrategyErr := c.updateStrategy(syncCtx.Context, strategy) - if updateStrategyErr != nil { - plog.Error("error while updating the CredentialIssuer status", err) - if err == nil { - err = updateStrategyErr - } - } + err = utilerrors.NewAggregate([]error{err, issuerconfig.Update( + syncCtx.Context, + c.pinnipedAPIClient, + credIssuer, + *strategy, + )}) if err == nil { plog.Debug("Successfully finished impersonatorConfigController Sync") @@ -196,10 +201,10 @@ type certNameInfo struct { clientEndpoint string } -func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v1alpha1.CredentialIssuerStrategy, error) { +func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context, credIssuer *v1alpha1.CredentialIssuer) (*v1alpha1.CredentialIssuerStrategy, error) { ctx := syncCtx.Context - config, err := c.loadImpersonationProxyConfiguration() + impersonationSpec, err := c.loadImpersonationProxyConfiguration(credIssuer) if err != nil { return nil, err } @@ -217,7 +222,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v plog.Debug("Queried for control plane nodes", "foundControlPlaneNodes", hasControlPlaneNodes) } - if c.shouldHaveImpersonator(config) { + if c.shouldHaveImpersonator(impersonationSpec) { if err = c.ensureImpersonatorIsStarted(syncCtx); err != nil { return nil, err } @@ -227,8 +232,8 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v } } - if c.shouldHaveLoadBalancer(config) { - if err = c.ensureLoadBalancerIsStarted(ctx, config); err != nil { + if c.shouldHaveLoadBalancer(impersonationSpec) { + if err = c.ensureLoadBalancerIsStarted(ctx, impersonationSpec); err != nil { return nil, err } } else { @@ -237,8 +242,8 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v } } - if c.shouldHaveClusterIPService(config) { - if err = c.ensureClusterIPServiceIsStarted(ctx, config); err != nil { + if c.shouldHaveClusterIPService(impersonationSpec) { + if err = c.ensureClusterIPServiceIsStarted(ctx, impersonationSpec); err != nil { return nil, err } } // else { // TODO test stopping the cluster ip service @@ -247,7 +252,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v // } //} - nameInfo, err := c.findDesiredTLSCertificateName(config) + nameInfo, err := c.findDesiredTLSCertificateName(impersonationSpec) if err != nil { // Unexpected error while determining the name that should go into the certs, so clear any existing certs. c.tlsServingCertDynamicCertProvider.UnsetCertKeyContent() @@ -255,7 +260,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v } var impersonationCA *certauthority.CA - if c.shouldHaveTLSSecret(config) { + if c.shouldHaveTLSSecret(impersonationSpec) { if impersonationCA, err = c.ensureCASecretIsCreated(ctx); err != nil { return nil, err } @@ -266,7 +271,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v return nil, err } - credentialIssuerStrategyResult := c.doSyncResult(nameInfo, config, impersonationCA) + credentialIssuerStrategyResult := c.doSyncResult(nameInfo, impersonationSpec, impersonationCA) if err = c.loadSignerCA(credentialIssuerStrategyResult.Status); err != nil { return nil, err @@ -275,12 +280,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v return credentialIssuerStrategyResult, nil } -func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*v1alpha1.ImpersonationProxySpec, error) { - credIssuer, err := c.credIssuerInformer.Lister().Get(c.credentialIssuerResourceName) - if err != nil { - return nil, fmt.Errorf("failed to get %s CredentialIssuer: %w", c.credentialIssuerResourceName, err) - } - +func (c *impersonatorConfigController) loadImpersonationProxyConfiguration(credIssuer *v1alpha1.CredentialIssuer) (*v1alpha1.ImpersonationProxySpec, error) { // Make a copy of the spec since we got this object from informer cache. spec := credIssuer.Spec.DeepCopy().ImpersonationProxy if spec == nil { @@ -329,11 +329,6 @@ func (c *impersonatorConfigController) shouldHaveTLSSecret(config *v1alpha1.Impe return c.shouldHaveImpersonator(config) } -func (c *impersonatorConfigController) updateStrategy(ctx context.Context, strategy *v1alpha1.CredentialIssuerStrategy) error { - // TODO use informer client rather than api client for reading - return issuerconfig.UpdateStrategy(ctx, c.credentialIssuerResourceName, c.labels, c.pinnipedAPIClient, *strategy) -} - func (c *impersonatorConfigController) loadBalancerExists() (bool, error) { _, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName) notFound := k8serrors.IsNotFound(err) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 87ac1495..db9b06ea 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -554,14 +554,10 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { controllerlib.TestRunSynchronously(t, subject) } - var addCredentialIssuerToTracker = func(resourceName string, credIssuerSpec v1alpha1.CredentialIssuerSpec, client *pinnipedfake.Clientset) { - t.Logf("adding CredentialIssuer %s to informer clientset", resourceName) - r.NoError(client.Tracker().Add(&v1alpha1.CredentialIssuer{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - }, - Spec: credIssuerSpec, - })) + var addCredentialIssuerToTrackers = func(credIssuer v1alpha1.CredentialIssuer, informerClient *pinnipedfake.Clientset, mainClient *pinnipedfake.Clientset) { + t.Logf("adding CredentialIssuer %s to informer and main clientsets", credIssuer.Name) + r.NoError(informerClient.Tracker().Add(&credIssuer)) + r.NoError(mainClient.Tracker().Add(&credIssuer)) } var newSecretWithData = func(resourceName string, data map[string][]byte) *corev1.Secret { @@ -856,7 +852,6 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // As long as we get the final result that we wanted then we are happy for the purposes // of this test. credentialIssuer := getCredentialIssuer() - r.Equal(labels, credentialIssuer.Labels) r.Equal([]v1alpha1.CredentialIssuerStrategy{expectedStrategy}, credentialIssuer.Status.Strategies) } @@ -1023,7 +1018,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it("errors and does nothing else", func() { startInformersAndController() - r.EqualError(runControllerSync(), "failed to get some-credential-issuer-resource-name CredentialIssuer: credentialissuer.config.concierge.pinniped.dev \"some-credential-issuer-resource-name\" not found") + r.EqualError(runControllerSync(), `could not get CredentialIssuer to update: credentialissuer.config.concierge.pinniped.dev "some-credential-issuer-resource-name" not found`) requireTLSServerWasNeverStarted() r.Len(kubeAPIClient.Actions(), 0) }) @@ -1033,15 +1028,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the configuration is auto mode with an endpoint and service type none", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, - ExternalEndpoint: localhostIP, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) when("there are visible control plane nodes", func() { @@ -1082,11 +1080,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the configuration is auto mode", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) when("there are visible control plane nodes", func() { @@ -1380,11 +1381,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the configuration is disabled mode", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeDisabled, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1405,11 +1409,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) when("no load balancer", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) }) @@ -1436,11 +1443,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("a loadbalancer already exists", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) @@ -1469,11 +1479,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("a load balancer and a secret already exists", func() { var caCrt []byte it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) ca := newCA() caSecret := newActualCASecret(ca, caSecretName) @@ -1499,15 +1512,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("credentialissuer has service type loadbalancer and custom annotations", func() { annotations := map[string]string{"some-annotation-key": "some-annotation-value"} it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, - Annotations: annotations, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + Annotations: annotations, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1528,15 +1544,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has a hostname specified and service type none", func() { const fakeHostname = "fake.example.com" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1557,15 +1576,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has a hostname specified and service type loadbalancer", func() { const fakeHostname = "fake.example.com" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1586,14 +1608,17 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has a hostname specified and service type clusterip", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1614,15 +1639,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has a endpoint which is an IP address with a port", func() { const fakeIPWithPort = "127.0.0.1:3000" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeIPWithPort, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeIPWithPort, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1643,15 +1671,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has a endpoint which is a hostname with a port, service type none", func() { const fakeHostnameWithPort = "fake.example.com:3000" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostnameWithPort, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostnameWithPort, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1672,16 +1703,19 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has a endpoint which is a hostname with a port, service type loadbalancer with loadbalancerip", func() { const fakeHostnameWithPort = "fake.example.com:3000" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostnameWithPort, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, - LoadBalancerIP: localhostIP, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostnameWithPort, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + LoadBalancerIP: localhostIP, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1725,7 +1759,10 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { } it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, ipAddressConfig, pinnipedInformerClient) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: ipAddressConfig, + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -1779,15 +1816,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the TLS cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { const fakeHostname = "fake.example.com" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) startInformersAndController() }) @@ -1824,15 +1864,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CA cert goes missing and needs to be recreated, e.g. when a user manually deleted it", func() { const fakeHostname = "fake.example.com" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) startInformersAndController() }) @@ -1872,15 +1915,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const fakeHostname = "fake.example.com" var caCrt []byte it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) startInformersAndController() r.NoError(runControllerSync()) @@ -1944,11 +1990,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the configuration switches from enabled to disabled mode", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -2004,15 +2053,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the endpoint and mode switch from specified with no service, to not specified, to specified again", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: localhostIP, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -2099,15 +2151,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("requesting a load balancer via CredentialIssuer, then updating the annotations", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: localhostIP, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -2157,15 +2212,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("requesting a load balancer via CredentialIssuer, then adding a static loadBalancerIP to the spec", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: localhostIP, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -2217,11 +2275,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("only starts the impersonator once and only lists the cluster's nodes once", func() { @@ -2335,15 +2396,19 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, - }, - }, pinnipedInformerClient) - r.NoError(pinnipedAPIClient.Tracker().Add(&v1alpha1.CredentialIssuer{ + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, - Status: v1alpha1.CredentialIssuerStatus{Strategies: []v1alpha1.CredentialIssuerStrategy{preExistingStrategy}}, - })) + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, + }, + Status: v1alpha1.CredentialIssuerStatus{ + Strategies: []v1alpha1.CredentialIssuerStrategy{ + preExistingStrategy, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -2362,11 +2427,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("getting the control plane nodes returns an error, e.g. when there are no nodes", func() { it("returns an error", func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) startInformersAndController() r.EqualError(runControllerSync(), "no nodes found") requireCredentialIssuer(newErrorStrategy("no nodes found")) @@ -2379,11 +2447,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addNodeWithRoleToTracker("worker", kubeAPIClient) impersonatorFuncReturnedFuncError = errors.New("some immediate impersonator startup error") - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("causes an immediate resync, returns an error on that next sync, and then restarts the server in a following sync", func() { @@ -2439,11 +2510,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the impersonator server dies for no apparent reason after running for a while", func() { it.Before(func() { addNodeWithRoleToTracker("worker", kubeAPIClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("causes an immediate resync, returns an error on that next sync, and then restarts the server in a following sync", func() { @@ -2495,9 +2569,12 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has nil impersonation spec", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: nil, - }, pinnipedInformerClient) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: nil, + }, + }, pinnipedInformerClient, pinnipedAPIClient) }) it("returns an error", func() { @@ -2512,11 +2589,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has invalid mode", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: "not-valid", + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: "not-valid", + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("returns an error", func() { @@ -2531,14 +2611,17 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has invalid service type", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: "not-valid", + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: "not-valid", + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("returns an error", func() { @@ -2553,14 +2636,17 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has invalid LoadBalancerIP", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - LoadBalancerIP: "invalid-ip-address", + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + LoadBalancerIP: "invalid-ip-address", + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("returns an error", func() { @@ -2575,12 +2661,15 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CredentialIssuer has invalid ExternalEndpoint", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: "[invalid", + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "[invalid", + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("returns an error", func() { @@ -2599,11 +2688,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { kubeAPIClient.PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { return true, nil, fmt.Errorf("error on create") }) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("returns an error", func() { @@ -2617,15 +2709,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("there is an error creating the tls secret", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: "example.com", - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "example.com", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) kubeAPIClient.PrependReactor("create", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { createdSecret := action.(coretesting.CreateAction).GetObject().(*corev1.Secret) @@ -2651,15 +2746,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("there is an error creating the CA secret", func() { it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: "example.com", - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "example.com", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) kubeAPIClient.PrependReactor("create", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { createdSecret := action.(coretesting.CreateAction).GetObject().(*corev1.Secret) @@ -2685,15 +2783,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the CA secret exists but is invalid while the TLS secret needs to be created", func() { it.Before(func() { addNodeWithRoleToTracker("control-plane", kubeAPIClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: "example.com", - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "example.com", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addSecretToTrackers(newEmptySecret(caSecretName), kubeAPIClient, kubeInformerClient) }) @@ -2715,11 +2816,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) addSecretToTrackers(newEmptySecret(tlsSecretName), kubeAPIClient, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) startInformersAndController() kubeAPIClient.PrependReactor("delete", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { return true, nil, fmt.Errorf("error on delete") @@ -2742,11 +2846,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addNodeWithRoleToTracker("control-plane", kubeAPIClient) addSecretToTrackers(newEmptySecret(tlsSecretName), kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeDisabled, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("does not pass the not found error through", func() { @@ -2764,15 +2871,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the PEM formatted data in the TLS Secret is not a valid cert", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: localhostIP, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) tlsSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -2827,11 +2937,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var caCrt []byte it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) ca := newCA() caSecret := newActualCASecret(ca, caSecretName) @@ -2890,11 +3003,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { addSecretToTrackers(tlsSecret, kubeAPIClient, kubeInformerClient) addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeInformerClient) addLoadBalancerServiceWithIngressToTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: localhostIP}}, kubeAPIClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) }) it("deletes the invalid certs, creates new certs, and starts the impersonator", func() { @@ -2935,19 +3051,22 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) addNodeWithRoleToTracker("worker", kubeAPIClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeAuto, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + }, }, - }, pinnipedInformerClient) - pinnipedAPIClient.PrependReactor("create", "credentialissuers", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - return true, nil, fmt.Errorf("error on create") + }, pinnipedInformerClient, pinnipedAPIClient) + pinnipedAPIClient.PrependReactor("update", "credentialissuers", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, fmt.Errorf("error on update") }) }) it("returns the error", func() { startInformersAndController() - r.EqualError(runControllerSync(), "could not create or update credentialissuer: create failed: error on create") + r.EqualError(runControllerSync(), "failed to update CredentialIssuer status: error on update") }) when("there is also a more fundamental error while starting the impersonator", func() { @@ -2957,9 +3076,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) - it("returns the more fundamental error instead of the CredentialIssuer error", func() { + it("returns both errors", func() { startInformersAndController() - r.EqualError(runControllerSync(), "error on service creation") + r.EqualError(runControllerSync(), "[error on service creation, failed to update CredentialIssuer status: error on update]") }) }) }) @@ -2967,15 +3086,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the impersonator is ready but there is a problem with the signing secret, which should be created by another controller", func() { const fakeHostname = "foo.example.com" it.Before(func() { - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: fakeHostname, - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("worker", kubeAPIClient) }) @@ -3059,15 +3181,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("the impersonator is enabled but the service type is none and the external endpoint is empty", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - ExternalEndpoint: "", - Service: v1alpha1.ImpersonationProxyServiceSpec{ - Type: v1alpha1.ImpersonationProxyServiceTypeNone, + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, }, }, - }, pinnipedInformerClient) + }, pinnipedInformerClient, pinnipedAPIClient) addNodeWithRoleToTracker("control-plane", kubeAPIClient) }) @@ -3078,7 +3203,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) }) - }, spec.Report(report.Terminal{})) + }, spec.Report(report.Terminal{})) // TODO: replace the Parallel() call here } type testQueue struct { diff --git a/internal/controller/issuerconfig/create_or_update_credential_issuer_config.go b/internal/controller/issuerconfig/create_or_update_credential_issuer_config.go deleted file mode 100644 index 7d936b3a..00000000 --- a/internal/controller/issuerconfig/create_or_update_credential_issuer_config.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package issuerconfig - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/api/equality" - - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/util/retry" - - configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" - pinnipedclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned" -) - -func CreateOrUpdateCredentialIssuerStatus( - ctx context.Context, - credentialIssuerResourceName string, - credentialIssuerLabels map[string]string, - pinnipedClient pinnipedclientset.Interface, - applyUpdatesToCredentialIssuerFunc func(configToUpdate *configv1alpha1.CredentialIssuerStatus), -) error { - err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - existingCredentialIssuer, err := pinnipedClient. - ConfigV1alpha1(). - CredentialIssuers(). - Get(ctx, credentialIssuerResourceName, metav1.GetOptions{}) - - notFound := k8serrors.IsNotFound(err) - if err != nil && !notFound { - return fmt.Errorf("get failed: %w", err) - } - - credentialIssuersClient := pinnipedClient.ConfigV1alpha1().CredentialIssuers() - - if notFound { - // create an empty credential issuer - minCredentialIssuer := minimalValidCredentialIssuer(credentialIssuerResourceName, credentialIssuerLabels) - - newCredentialIssuer, err := credentialIssuersClient.Create(ctx, minCredentialIssuer, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("create failed: %w", err) - } - - existingCredentialIssuer = newCredentialIssuer - } - - // check to see if we need to update the status - credentialIssuer := existingCredentialIssuer.DeepCopy() - applyUpdatesToCredentialIssuerFunc(&credentialIssuer.Status) - - if equality.Semantic.DeepEqual(existingCredentialIssuer, credentialIssuer) { - // Nothing interesting would change as a result of this update, so skip it - return nil - } - - if _, err := credentialIssuersClient.UpdateStatus(ctx, credentialIssuer, metav1.UpdateOptions{}); err != nil { - return err - } - - return nil - }) - - if err != nil { - return fmt.Errorf("could not create or update credentialissuer: %w", err) - } - return nil -} - -func minimalValidCredentialIssuer( - credentialIssuerName string, - credentialIssuerLabels map[string]string, -) *configv1alpha1.CredentialIssuer { - return &configv1alpha1.CredentialIssuer{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: credentialIssuerName, - Labels: credentialIssuerLabels, - }, - } -} diff --git a/internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go b/internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go deleted file mode 100644 index beb20e56..00000000 --- a/internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package issuerconfig - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - coretesting "k8s.io/client-go/testing" - apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" - - configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" - pinnipedfake "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake" -) - -func TestCreateOrUpdateCredentialIssuerStatus(t *testing.T) { - spec.Run(t, "specs", func(t *testing.T, when spec.G, it spec.S) { - var r *require.Assertions - var ctx context.Context - var pinnipedAPIClient *pinnipedfake.Clientset - var credentialIssuerGVR schema.GroupVersionResource - const credentialIssuerResourceName = "some-resource-name" - - it.Before(func() { - r = require.New(t) - ctx = context.Background() - pinnipedAPIClient = pinnipedfake.NewSimpleClientset() - credentialIssuerGVR = schema.GroupVersionResource{ - Group: configv1alpha1.GroupName, - Version: configv1alpha1.SchemeGroupVersion.Version, - Resource: "credentialissuers", - } - }) - - when("the config does not exist", func() { - it("creates a new config and then updates it with the func parameter", func() { - err := CreateOrUpdateCredentialIssuerStatus( - ctx, - credentialIssuerResourceName, - map[string]string{ - "myLabelKey1": "myLabelValue1", - "myLabelKey2": "myLabelValue2", - }, - pinnipedAPIClient, - func(configToUpdate *configv1alpha1.CredentialIssuerStatus) { - configToUpdate.KubeConfigInfo = &configv1alpha1.CredentialIssuerKubeConfigInfo{ - CertificateAuthorityData: "some-ca-value", - } - }, - ) - r.NoError(err) - - expectedGetAction := coretesting.NewRootGetAction(credentialIssuerGVR, credentialIssuerResourceName) - - expectedCreateAction := coretesting.NewRootCreateAction( - credentialIssuerGVR, - &configv1alpha1.CredentialIssuer{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: credentialIssuerResourceName, - Labels: map[string]string{ - "myLabelKey1": "myLabelValue1", - "myLabelKey2": "myLabelValue2", - }, - }, - }, - ) - - expectedUpdateAction := coretesting.NewRootUpdateSubresourceAction(credentialIssuerGVR, "status", - &configv1alpha1.CredentialIssuer{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: credentialIssuerResourceName, - Labels: map[string]string{ - "myLabelKey1": "myLabelValue1", - "myLabelKey2": "myLabelValue2", - }, - }, - Status: configv1alpha1.CredentialIssuerStatus{ - KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ - Server: "", - CertificateAuthorityData: "some-ca-value", - }, - }, - }, - ) - - r.Equal([]coretesting.Action{expectedGetAction, expectedCreateAction, expectedUpdateAction}, pinnipedAPIClient.Actions()) - }) - - when("there is an unexpected error while creating the existing object", func() { - it.Before(func() { - pinnipedAPIClient.PrependReactor("create", "credentialissuers", func(_ coretesting.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("error on create") - }) - }) - - it("returns an error", func() { - err := CreateOrUpdateCredentialIssuerStatus( - ctx, - credentialIssuerResourceName, - map[string]string{}, - pinnipedAPIClient, - func(configToUpdate *configv1alpha1.CredentialIssuerStatus) {}, - ) - r.EqualError(err, "could not create or update credentialissuer: create failed: error on create") - }) - }) - }) - - when("the config already exists", func() { - var existingConfig *configv1alpha1.CredentialIssuer - - it.Before(func() { - existingConfig = &configv1alpha1.CredentialIssuer{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: credentialIssuerResourceName, - Labels: map[string]string{ - "myLabelKey1": "myLabelValue1", - }, - }, - Status: configv1alpha1.CredentialIssuerStatus{ - Strategies: []configv1alpha1.CredentialIssuerStrategy{ - { - Type: configv1alpha1.KubeClusterSigningCertificateStrategyType, - Status: configv1alpha1.SuccessStrategyStatus, - Reason: configv1alpha1.FetchedKeyStrategyReason, - Message: "initial-message", - LastUpdateTime: metav1.Now(), - }, - }, - KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ - Server: "initial-server-value", - CertificateAuthorityData: "initial-ca-value", - }, - }, - } - r.NoError(pinnipedAPIClient.Tracker().Add(existingConfig)) - }) - - it("updates the existing config to only apply the updates made by the func parameter", func() { - err := CreateOrUpdateCredentialIssuerStatus( - ctx, - credentialIssuerResourceName, - map[string]string{ - "myLabelKey1": "myLabelValue1", - "myLabelKey2": "myLabelValue2", - }, - pinnipedAPIClient, - func(configToUpdate *configv1alpha1.CredentialIssuerStatus) { - configToUpdate.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" - }, - ) - r.NoError(err) - - expectedGetAction := coretesting.NewRootGetAction(credentialIssuerGVR, credentialIssuerResourceName) - - // Only the edited field should be changed. - expectedUpdatedConfig := existingConfig.DeepCopy() - expectedUpdatedConfig.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" - expectedUpdateAction := coretesting.NewRootUpdateSubresourceAction(credentialIssuerGVR, "status", expectedUpdatedConfig) - - r.Equal([]coretesting.Action{expectedGetAction, expectedUpdateAction}, pinnipedAPIClient.Actions()) - }) - - it("avoids the cost of an update if the local updates made by the func parameter did not actually change anything", func() { - err := CreateOrUpdateCredentialIssuerStatus( - ctx, - credentialIssuerResourceName, - map[string]string{}, - pinnipedAPIClient, - func(configToUpdate *configv1alpha1.CredentialIssuerStatus) { - configToUpdate.KubeConfigInfo.CertificateAuthorityData = "initial-ca-value" - - t := configToUpdate.Strategies[0].LastUpdateTime - loc, err := time.LoadLocation("Asia/Shanghai") - r.NoError(err) - configToUpdate.Strategies[0].LastUpdateTime = metav1.NewTime(t.In(loc)) - }, - ) - r.NoError(err) - - expectedGetAction := coretesting.NewRootGetAction(credentialIssuerGVR, credentialIssuerResourceName) - r.Equal([]coretesting.Action{expectedGetAction}, pinnipedAPIClient.Actions()) - }) - - when("there is an unexpected error while getting the existing object", func() { - it.Before(func() { - pinnipedAPIClient.PrependReactor("get", "credentialissuers", func(_ coretesting.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("error on get") - }) - }) - - it("returns an error", func() { - err := CreateOrUpdateCredentialIssuerStatus( - ctx, - credentialIssuerResourceName, - map[string]string{}, - pinnipedAPIClient, - func(configToUpdate *configv1alpha1.CredentialIssuerStatus) {}, - ) - r.EqualError(err, "could not create or update credentialissuer: get failed: error on get") - }) - }) - - when("there is an unexpected error while updating the existing object", func() { - it.Before(func() { - pinnipedAPIClient.PrependReactor("update", "credentialissuers", func(_ coretesting.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("error on update") - }) - }) - - it("returns an error", func() { - err := CreateOrUpdateCredentialIssuerStatus( - ctx, - credentialIssuerResourceName, - map[string]string{}, - pinnipedAPIClient, - func(configToUpdate *configv1alpha1.CredentialIssuerStatus) { - configToUpdate.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" - }, - ) - r.EqualError(err, "could not create or update credentialissuer: error on update") - }) - }) - - when("there is a conflict error while updating the existing object on the first try and the next try succeeds", func() { - var slightlyDifferentExistingConfig *configv1alpha1.CredentialIssuer - - it.Before(func() { - hit := false - slightlyDifferentExistingConfig = existingConfig.DeepCopy() - slightlyDifferentExistingConfig.Status.KubeConfigInfo.Server = "some-other-server-value-from-conflicting-update" - - pinnipedAPIClient.PrependReactor("update", "credentialissuers", func(_ coretesting.Action) (bool, runtime.Object, error) { - // Return an error on the first call, then fall through to the default (successful) response. - if !hit { - // Before the update fails, also change the object that will be returned by the next Get(), - // to make sure that the production code does a fresh Get() after detecting a conflict. - r.NoError(pinnipedAPIClient.Tracker().Update(credentialIssuerGVR, slightlyDifferentExistingConfig, "")) - hit = true - return true, nil, apierrors.NewConflict(schema.GroupResource{ - Group: apiregistrationv1.GroupName, - Resource: "credentialissuers", - }, "alphav1.pinniped.dev", fmt.Errorf("there was a conflict")) - } - return false, nil, nil - }) - }) - - it("retries updates on conflict", func() { - err := CreateOrUpdateCredentialIssuerStatus( - ctx, - credentialIssuerResourceName, - map[string]string{ - "myLabelKey1": "myLabelValue1", - "myLabelKey2": "myLabelValue2", - }, - pinnipedAPIClient, - func(configToUpdate *configv1alpha1.CredentialIssuerStatus) { - configToUpdate.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" - }, - ) - r.NoError(err) - - expectedGetAction := coretesting.NewRootGetAction(credentialIssuerGVR, credentialIssuerResourceName) - - // The first attempted update only includes its own edits. - firstExpectedUpdatedConfig := existingConfig.DeepCopy() - firstExpectedUpdatedConfig.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" - firstExpectedUpdateAction := coretesting.NewRootUpdateSubresourceAction(credentialIssuerGVR, "status", firstExpectedUpdatedConfig) - - // Both the edits made by this update and the edits made by the conflicting update should be included. - secondExpectedUpdatedConfig := existingConfig.DeepCopy() - secondExpectedUpdatedConfig.Status.KubeConfigInfo.Server = "some-other-server-value-from-conflicting-update" - secondExpectedUpdatedConfig.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" - secondExpectedUpdateAction := coretesting.NewRootUpdateSubresourceAction(credentialIssuerGVR, "status", secondExpectedUpdatedConfig) - - expectedActions := []coretesting.Action{ - expectedGetAction, - firstExpectedUpdateAction, - expectedGetAction, - secondExpectedUpdateAction, - } - r.Equal(expectedActions, pinnipedAPIClient.Actions()) - }) - }) - }) - }, spec.Parallel(), spec.Report(report.Terminal{})) -} diff --git a/internal/controller/issuerconfig/doc.go b/internal/controller/issuerconfig/doc.go deleted file mode 100644 index a30f1283..00000000 --- a/internal/controller/issuerconfig/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2020 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package issuerconfig contains controller(s) for reconciling CredentialIssuer's. -package issuerconfig diff --git a/internal/controller/issuerconfig/update_strategy.go b/internal/controller/issuerconfig/issuerconfig.go similarity index 82% rename from internal/controller/issuerconfig/update_strategy.go rename to internal/controller/issuerconfig/issuerconfig.go index 5d079136..b2440203 100644 --- a/internal/controller/issuerconfig/update_strategy.go +++ b/internal/controller/issuerconfig/issuerconfig.go @@ -1,6 +1,7 @@ // Copyright 2021 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// Package issuerconfig contains helpers for updating CredentialIssuer status entries. package issuerconfig import ( @@ -15,23 +16,6 @@ import ( "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned" ) -// UpdateStrategy creates or updates the desired strategy in the CredentialIssuer status.strategies field. -// The CredentialIssuer will be created if it does not already exist. -func UpdateStrategy(ctx context.Context, - name string, - credentialIssuerLabels map[string]string, - pinnipedAPIClient versioned.Interface, - strategy v1alpha1.CredentialIssuerStrategy, -) error { - return CreateOrUpdateCredentialIssuerStatus( - ctx, - name, - credentialIssuerLabels, - pinnipedAPIClient, - func(configToUpdate *v1alpha1.CredentialIssuerStatus) { mergeStrategy(configToUpdate, strategy) }, - ) -} - // Update a strategy on an existing CredentialIssuer, merging into any existing strategy entries. func Update(ctx context.Context, client versioned.Interface, issuer *v1alpha1.CredentialIssuer, strategy v1alpha1.CredentialIssuerStrategy) error { // Update the existing object to merge in the new strategy. diff --git a/internal/controller/issuerconfig/update_strategy_test.go b/internal/controller/issuerconfig/issuerconfig_test.go similarity index 100% rename from internal/controller/issuerconfig/update_strategy_test.go rename to internal/controller/issuerconfig/issuerconfig_test.go From 62651eddb0fef7e686cc6d3210f1c48b8e25c1fe Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Thu, 20 May 2021 11:57:07 -0700 Subject: [PATCH 19/43] Took care of some impersonation cluster ip related todos --- .../impersonatorconfig/impersonator_config.go | 75 ++++-- .../impersonator_config_test.go | 225 ++++++++++++++---- 2 files changed, 239 insertions(+), 61 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 3fdbed78..8eb086b1 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -246,11 +246,11 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context, cre if err = c.ensureClusterIPServiceIsStarted(ctx, impersonationSpec); err != nil { return nil, err } - } // else { // TODO test stopping the cluster ip service - // if err = c.ensureClusterIPServiceIsStopped(ctx); err != nil { - // return nil, err - // } - //} + } else { + if err = c.ensureClusterIPServiceIsStopped(ctx); err != nil { + return nil, err + } + } nameInfo, err := c.findDesiredTLSCertificateName(impersonationSpec) if err != nil { @@ -341,6 +341,18 @@ func (c *impersonatorConfigController) loadBalancerExists() (bool, error) { return true, nil } +func (c *impersonatorConfigController) clusterIPExists() (bool, error) { + _, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedClusterIPServiceName) + notFound := k8serrors.IsNotFound(err) + if notFound { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + func (c *impersonatorConfigController) loadBalancerNeedsUpdate(config *v1alpha1.ImpersonationProxySpec) (bool, error) { lb, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName) if err != nil { @@ -524,11 +536,14 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx conte Annotations: config.Service.Annotations, }, } - //running, err := c.ClusterIPExists() // TODO test that clusterip is only created once - //if err != nil { - // return err + running, _ := c.clusterIPExists() + if running { + return nil + } + // if err != nil { + // return err // TODO test this error case //} - //if running { + // if running { // needsUpdate, err := c.ClusterIPNeedsUpdate(config) // TODO test updating annotations on clusterip // if err != nil { // return err @@ -541,7 +556,7 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx conte // return err // } // return nil - //} + // } plog.Info("creating cluster ip for impersonation proxy", "service", c.generatedClusterIPServiceName, "namespace", c.namespace) @@ -549,6 +564,21 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx conte return err } +func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx context.Context) error { + running, err := c.clusterIPExists() + if err != nil { + return err + } + if !running { + return nil + } + + plog.Info("Deleting cluster ip for impersonation proxy", + "service", c.generatedClusterIPServiceName, + "namespace", c.namespace) + return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{}) +} + func (c *impersonatorConfigController) ensureTLSSecret(ctx context.Context, nameInfo *certNameInfo, ca *certauthority.CA) error { secretFromInformer, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) @@ -750,10 +780,9 @@ func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1a // - clusterip and no external endpoint if config.ExternalEndpoint != "" { return c.findTLSCertificateNameFromEndpointConfig(config), nil - } // else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { - // // c.findTLSCertificateNameFromClusterIPService() - // // TODO implement this - //} + } else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { + return c.findTLSCertificateNameFromClusterIPService() + } return c.findTLSCertificateNameFromLoadBalancer() } @@ -801,6 +830,24 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromLoadBalancer() return nil, fmt.Errorf("could not find valid IP addresses or hostnames from load balancer %s/%s", c.namespace, lb.Name) } +func (c *impersonatorConfigController) findTLSCertificateNameFromClusterIPService() (*certNameInfo, error) { + clusterIP, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedClusterIPServiceName) + notFound := k8serrors.IsNotFound(err) + if notFound { + // We aren't ready and will try again later in this case. + return &certNameInfo{ready: false}, nil + } + if err != nil { + return nil, err + } + ip := clusterIP.Spec.ClusterIP + if ip != "" { + parsedIP := net.ParseIP(ip) + return &certNameInfo{ready: true, selectedIP: parsedIP, clientEndpoint: ip}, nil + } + return &certNameInfo{ready: false}, nil +} + func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, ca *certauthority.CA, ip net.IP, hostname string) (*v1.Secret, error) { var hostnames []string var ips []net.IP diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index db9b06ea..55fca00f 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -632,6 +632,17 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { } } + var newClusterIPService = func(resourceName string, status corev1.ServiceStatus, spec corev1.ServiceSpec) *corev1.Service { + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: installedInNamespace, + }, + Spec: spec, + Status: status, + } + } + // Anytime an object is added/updated/deleted in the informer's client *after* the informer is started, then we // need to wait for the informer's cache to asynchronously pick up that change from its "watch". // If an object is added to the informer's client *before* the informer is started, then waiting is @@ -728,6 +739,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(client.Tracker().Add(loadBalancerService)) } + var addClusterIPServiceToTracker = func(resourceName string, clusterIP string, client *kubernetesfake.Clientset) { + clusterIPService := newClusterIPService(resourceName, corev1.ServiceStatus{}, corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + ClusterIP: clusterIP, + }) + r.NoError(client.Tracker().Add(clusterIPService)) + } + var addSecretToTrackers = func(secret *corev1.Secret, clients ...*kubernetesfake.Clientset) { for _, client := range clients { secretCopy := secret.DeepCopy() @@ -900,6 +919,15 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal(labels, createdClusterIPService.Labels) } + var requireClusterIPWasDeleted = func(action coretesting.Action) { + // TODO maybe de-dup this with loadbalancerwasdeleted + deleteAction, ok := action.(coretesting.DeleteAction) + r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) + r.Equal("delete", deleteAction.GetVerb()) + r.Equal(clusterIPServiceName, deleteAction.GetName()) + r.Equal("services", deleteAction.GetResource().Resource) + } + var requireTLSSecretWasDeleted = func(action coretesting.Action) { deleteAction, ok := action.(coretesting.DeleteAction) r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) @@ -1476,6 +1504,38 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("a clusterip already exists with ingress", func() { + const fakeIP = "127.0.0.123" + it.Before(func() { + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + addClusterIPServiceToTracker(clusterIPServiceName, fakeIP, kubeInformerClient) + addClusterIPServiceToTracker(clusterIPServiceName, fakeIP, kubeAPIClient) + }) + + it("starts the impersonator without creating a clusterip", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + ":443": testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeIP, ca)) + // requireSigningCertProviderHasLoadedCerts() + }) + }) + when("a load balancer and a secret already exists", func() { var caCrt []byte it.Before(func() { @@ -1988,65 +2048,136 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) when("the configuration switches from enabled to disabled mode", func() { - it.Before(func() { - addSecretToTrackers(signingCASecret, kubeInformerClient) - addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ - ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, - Spec: v1alpha1.CredentialIssuerSpec{ + when("service type loadbalancer", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator and loadbalancer, then shuts it down, then starts it again", func() { + startInformersAndController() + + r.NoError(runControllerSync()) + requireTLSServerIsRunningWithoutCerts() + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + + // Update the CredentialIssuer. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + requireTLSServerIsNoLongerRunning() + r.Len(kubeAPIClient.Actions(), 4) + requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[3]) + requireCredentialIssuer(newManuallyDisabledStrategy()) + requireSigningCertProviderIsEmpty() + + deleteServiceFromTracker(loadBalancerServiceName, kubeInformerClient) + waitForObjectToBeDeletedFromInformer(loadBalancerServiceName, kubeInformers.Core().V1().Services()) + + // Update the CredentialIssuer again. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ Mode: v1alpha1.ImpersonationProxyModeEnabled, }, - }, - }, pinnipedInformerClient, pinnipedAPIClient) - addNodeWithRoleToTracker("worker", kubeAPIClient) + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + requireTLSServerIsRunningWithoutCerts() + r.Len(kubeAPIClient.Actions(), 5) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[4]) + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + }) }) - it("starts the impersonator and loadbalancer, then shuts it down, then starts it again", func() { - startInformersAndController() + when("service type clusterip", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) - r.NoError(runControllerSync()) - requireTLSServerIsRunningWithoutCerts() - r.Len(kubeAPIClient.Actions(), 3) - requireNodesListed(kubeAPIClient.Actions()[0]) - requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) - requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireCredentialIssuer(newPendingStrategy()) - requireSigningCertProviderIsEmpty() + it("starts the impersonator and clusterip, then shuts it down, then starts it again", func() { + startInformersAndController() - // Simulate the informer cache's background update from its watch. - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) - addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + r.NoError(runControllerSync()) + requireTLSServerIsRunningWithoutCerts() + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireClusterIPWasCreated(kubeAPIClient.Actions()[1]) + requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() - // Update the CredentialIssuer. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeDisabled, - }, - }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) - r.NoError(runControllerSync()) - requireTLSServerIsNoLongerRunning() - r.Len(kubeAPIClient.Actions(), 4) - requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[3]) - requireCredentialIssuer(newManuallyDisabledStrategy()) - requireSigningCertProviderIsEmpty() + // Update the CredentialIssuer. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) - deleteServiceFromTracker(loadBalancerServiceName, kubeInformerClient) - waitForObjectToBeDeletedFromInformer(loadBalancerServiceName, kubeInformers.Core().V1().Services()) + r.NoError(runControllerSync()) + requireTLSServerIsNoLongerRunning() + r.Len(kubeAPIClient.Actions(), 4) + requireClusterIPWasDeleted(kubeAPIClient.Actions()[3]) + requireCredentialIssuer(newManuallyDisabledStrategy()) + requireSigningCertProviderIsEmpty() - // Update the CredentialIssuer again. - updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ - ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ - Mode: v1alpha1.ImpersonationProxyModeEnabled, - }, - }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + deleteServiceFromTracker(clusterIPServiceName, kubeInformerClient) + waitForObjectToBeDeletedFromInformer(clusterIPServiceName, kubeInformers.Core().V1().Services()) - r.NoError(runControllerSync()) - requireTLSServerIsRunningWithoutCerts() - r.Len(kubeAPIClient.Actions(), 5) - requireLoadBalancerWasCreated(kubeAPIClient.Actions()[4]) - requireCredentialIssuer(newPendingStrategy()) - requireSigningCertProviderIsEmpty() + // Update the CredentialIssuer again. + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + requireTLSServerIsRunningWithoutCerts() + r.Len(kubeAPIClient.Actions(), 5) + requireClusterIPWasCreated(kubeAPIClient.Actions()[4]) + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + }) }) }) From 599d70d6dcc33c0d8e1bd8f113b001ded33ee047 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Thu, 20 May 2021 14:11:35 -0700 Subject: [PATCH 20/43] Wire generatedClusterIPServiceName through from NamesConfig --- deploy/concierge/deployment.yaml | 1 + internal/config/concierge/config.go | 3 ++ internal/config/concierge/config_test.go | 32 ++++++++++++++++++- internal/config/concierge/types.go | 1 + .../controllermanager/prepare_controllers.go | 2 +- 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/deploy/concierge/deployment.yaml b/deploy/concierge/deployment.yaml index 33423efb..edef2fd8 100644 --- a/deploy/concierge/deployment.yaml +++ b/deploy/concierge/deployment.yaml @@ -51,6 +51,7 @@ data: apiService: (@= defaultResourceNameWithSuffix("api") @) impersonationConfigMap: (@= defaultResourceNameWithSuffix("impersonation-proxy-config") @) impersonationLoadBalancerService: (@= defaultResourceNameWithSuffix("impersonation-proxy-load-balancer") @) + impersonationClusterIPService: (@= defaultResourceNameWithSuffix("impersonation-proxy-cluster-ip") @) impersonationTLSCertificateSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-tls-serving-certificate") @) impersonationCACertificateSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-ca-certificate") @) impersonationSignerSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-signer-ca-certificate") @) diff --git a/internal/config/concierge/config.go b/internal/config/concierge/config.go index ad6a00df..bb7fbee8 100644 --- a/internal/config/concierge/config.go +++ b/internal/config/concierge/config.go @@ -114,6 +114,9 @@ func validateNames(names *NamesConfigSpec) error { if names.ImpersonationLoadBalancerService == "" { missingNames = append(missingNames, "impersonationLoadBalancerService") } + if names.ImpersonationClusterIPService == "" { + missingNames = append(missingNames, "impersonationClusterIPService") + } if names.ImpersonationTLSCertificateSecret == "" { missingNames = append(missingNames, "impersonationTLSCertificateSecret") } diff --git a/internal/config/concierge/config_test.go b/internal/config/concierge/config_test.go index baebf8c3..5d4097ea 100644 --- a/internal/config/concierge/config_test.go +++ b/internal/config/concierge/config_test.go @@ -40,6 +40,7 @@ func TestFromPath(t *testing.T) { kubeCertAgentPrefix: kube-cert-agent-prefix impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value @@ -71,6 +72,7 @@ func TestFromPath(t *testing.T) { APIService: "pinniped-api", ImpersonationConfigMap: "impersonationConfigMap-value", ImpersonationLoadBalancerService: "impersonationLoadBalancerService-value", + ImpersonationClusterIPService: "impersonationClusterIPService-value", ImpersonationTLSCertificateSecret: "impersonationTLSCertificateSecret-value", ImpersonationCACertificateSecret: "impersonationCACertificateSecret-value", ImpersonationSignerSecret: "impersonationSignerSecret-value", @@ -98,6 +100,7 @@ func TestFromPath(t *testing.T) { apiService: pinniped-api impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value @@ -120,6 +123,7 @@ func TestFromPath(t *testing.T) { APIService: "pinniped-api", ImpersonationConfigMap: "impersonationConfigMap-value", ImpersonationLoadBalancerService: "impersonationLoadBalancerService-value", + ImpersonationClusterIPService: "impersonationClusterIPService-value", ImpersonationTLSCertificateSecret: "impersonationTLSCertificateSecret-value", ImpersonationCACertificateSecret: "impersonationCACertificateSecret-value", ImpersonationSignerSecret: "impersonationSignerSecret-value", @@ -137,7 +141,7 @@ func TestFromPath(t *testing.T) { yaml: here.Doc(``), wantError: "validate names: missing required names: servingCertificateSecret, credentialIssuer, " + "apiService, impersonationConfigMap, impersonationLoadBalancerService, " + - "impersonationTLSCertificateSecret, impersonationCACertificateSecret, " + + "impersonationClusterIPService, impersonationTLSCertificateSecret, impersonationCACertificateSecret, " + "impersonationSignerSecret, agentServiceAccount", }, { @@ -149,6 +153,7 @@ func TestFromPath(t *testing.T) { credentialIssuer: pinniped-config impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value @@ -165,6 +170,7 @@ func TestFromPath(t *testing.T) { apiService: pinniped-api impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value @@ -181,6 +187,7 @@ func TestFromPath(t *testing.T) { apiService: pinniped-api impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value @@ -197,6 +204,7 @@ func TestFromPath(t *testing.T) { credentialIssuer: pinniped-config apiService: pinniped-api impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value @@ -213,6 +221,7 @@ func TestFromPath(t *testing.T) { credentialIssuer: pinniped-config apiService: pinniped-api impersonationConfigMap: impersonationConfigMap-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value @@ -220,6 +229,23 @@ func TestFromPath(t *testing.T) { `), wantError: "validate names: missing required names: impersonationLoadBalancerService", }, + { + name: "Missing impersonationClusterIPService name", + yaml: here.Doc(` + --- + names: + servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate + credentialIssuer: pinniped-config + apiService: pinniped-api + impersonationConfigMap: impersonationConfigMap-value + impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value + impersonationCACertificateSecret: impersonationCACertificateSecret-value + impersonationSignerSecret: impersonationSignerSecret-value + agentServiceAccount: agentServiceAccount-value + `), + wantError: "validate names: missing required names: impersonationClusterIPService", + }, { name: "Missing impersonationTLSCertificateSecret name", yaml: here.Doc(` @@ -230,6 +256,7 @@ func TestFromPath(t *testing.T) { apiService: pinniped-api impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationCACertificateSecret: impersonationCACertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value agentServiceAccount: agentServiceAccount-value @@ -246,6 +273,7 @@ func TestFromPath(t *testing.T) { apiService: pinniped-api impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationSignerSecret: impersonationSignerSecret-value agentServiceAccount: agentServiceAccount-value @@ -262,6 +290,7 @@ func TestFromPath(t *testing.T) { apiService: pinniped-api impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value agentServiceAccount: agentServiceAccount-value @@ -277,6 +306,7 @@ func TestFromPath(t *testing.T) { credentialIssuer: pinniped-config apiService: pinniped-api impersonationLoadBalancerService: impersonationLoadBalancerService-value + impersonationClusterIPService: impersonationClusterIPService-value impersonationSignerSecret: impersonationSignerSecret-value agentServiceAccount: agentServiceAccount-value `), diff --git a/internal/config/concierge/types.go b/internal/config/concierge/types.go index 4b4e56a2..ea174e65 100644 --- a/internal/config/concierge/types.go +++ b/internal/config/concierge/types.go @@ -40,6 +40,7 @@ type NamesConfigSpec struct { // TODO: remove this key entirely ImpersonationConfigMap string `json:"impersonationConfigMap"` ImpersonationLoadBalancerService string `json:"impersonationLoadBalancerService"` + ImpersonationClusterIPService string `json:"impersonationClusterIPService"` ImpersonationTLSCertificateSecret string `json:"impersonationTLSCertificateSecret"` ImpersonationCACertificateSecret string `json:"impersonationCACertificateSecret"` ImpersonationSignerSecret string `json:"impersonationSignerSecret"` diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index f5ea77cc..278b2438 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -261,7 +261,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { controllerlib.WithInformer, controllerlib.WithInitialEvent, c.NamesConfig.ImpersonationLoadBalancerService, - "impersonation-proxy-cluster-ip", // TODO wire this through from namesConfig + c.NamesConfig.ImpersonationClusterIPService, c.NamesConfig.ImpersonationTLSCertificateSecret, c.NamesConfig.ImpersonationCACertificateSecret, c.Labels, From 4606f1d8bd46f7597716029c6517e1c19b671f3c Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Thu, 20 May 2021 16:21:10 -0700 Subject: [PATCH 21/43] More error handling for cluster ip --- .../impersonatorconfig/impersonator_config.go | 47 +++--- .../impersonator_config_test.go | 136 +++++++++++++++++- 2 files changed, 163 insertions(+), 20 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 8eb086b1..63179559 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -368,6 +368,18 @@ func (c *impersonatorConfigController) loadBalancerNeedsUpdate(config *v1alpha1. return false, nil } +func (c *impersonatorConfigController) ClusterIPNeedsUpdate(config *v1alpha1.ImpersonationProxySpec) (bool, error) { + clusterIP, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedClusterIPServiceName) + if err != nil { + return false, err + } + // TODO this will break if anything other than pinniped is adding annotations + if !reflect.DeepEqual(clusterIP.Annotations, config.Service.Annotations) { + return true, nil + } + return false, nil +} + func (c *impersonatorConfigController) tlsSecretExists() (bool, *v1.Secret, error) { secret, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) @@ -536,31 +548,28 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx conte Annotations: config.Service.Annotations, }, } - running, _ := c.clusterIPExists() + running, err := c.clusterIPExists() + if err != nil { + return err + } if running { + needsUpdate, err := c.ClusterIPNeedsUpdate(config) + if err != nil { + return err + } + if needsUpdate { + plog.Info("updating cluster ip for impersonation proxy", + "service", c.generatedLoadBalancerServiceName, + "namespace", c.namespace) + _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &clusterIP, metav1.UpdateOptions{}) + return err + } return nil } - // if err != nil { - // return err // TODO test this error case - //} - // if running { - // needsUpdate, err := c.ClusterIPNeedsUpdate(config) // TODO test updating annotations on clusterip - // if err != nil { - // return err - // } - // if needsUpdate { - // plog.Info("updating load balancer for impersonation proxy", - // "service", c.generatedLoadBalancerServiceName, - // "namespace", c.namespace) - // _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &loadBalancer, metav1.UpdateOptions{}) - // return err - // } - // return nil - // } plog.Info("creating cluster ip for impersonation proxy", "service", c.generatedClusterIPServiceName, "namespace", c.namespace) - _, err := c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, &clusterIP, metav1.CreateOptions{}) + _, err = c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, &clusterIP, metav1.CreateOptions{}) return err } diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 55fca00f..2ed70277 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -908,7 +908,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { return updatedLoadBalancerService } - var requireClusterIPWasCreated = func(action coretesting.Action) { + var requireClusterIPWasCreated = func(action coretesting.Action) *corev1.Service { createAction, ok := action.(coretesting.CreateAction) r.True(ok, "should have been able to cast this action to CreateAction: %v", action) r.Equal("create", createAction.GetVerb()) @@ -917,6 +917,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal(corev1.ServiceTypeClusterIP, createdClusterIPService.Spec.Type) r.Equal("app-name", createdClusterIPService.Spec.Selector["app"]) r.Equal(labels, createdClusterIPService.Labels) + return createdClusterIPService } var requireClusterIPWasDeleted = func(action coretesting.Action) { @@ -928,6 +929,19 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal("services", deleteAction.GetResource().Resource) } + var requireClusterIPWasUpdated = func(action coretesting.Action) *corev1.Service { + updateAction, ok := action.(coretesting.UpdateAction) + r.True(ok, "should have been able to cast this action to UpdateAction: %v", action) + r.Equal("update", updateAction.GetVerb()) + updatedLoadBalancerService := updateAction.GetObject().(*corev1.Service) + r.Equal(clusterIPServiceName, updatedLoadBalancerService.Name) + r.Equal(installedInNamespace, updatedLoadBalancerService.Namespace) + r.Equal(corev1.ServiceTypeClusterIP, updatedLoadBalancerService.Spec.Type) + r.Equal("app-name", updatedLoadBalancerService.Spec.Selector["app"]) + r.Equal(labels, updatedLoadBalancerService.Labels) + return updatedLoadBalancerService + } + var requireTLSSecretWasDeleted = func(action coretesting.Action) { deleteAction, ok := action.(coretesting.DeleteAction) r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) @@ -2340,6 +2354,67 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("requesting a cluster ip via CredentialIssuer, then updating the annotations", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("creates the cluster ip without annotations, then adds them", func() { + startInformersAndController() + + // Should have started in "enabled" mode with service type load balancer, so one is created. + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireNodesListed(kubeAPIClient.Actions()[0]) + clusterIPService := requireClusterIPWasCreated(kubeAPIClient.Actions()[1]) + require.Equal(t, map[string]string(nil), clusterIPService.Annotations) // there should be no annotations at first + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + + // Simulate the informer cache's background update from its watch. + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[1], kubeInformers.Core().V1().Services()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[2], kubeInformers.Core().V1().Secrets()) + addObjectFromCreateActionToInformerAndWait(kubeAPIClient.Actions()[3], kubeInformers.Core().V1().Secrets()) + + // Add annotations to the spec. + annotations := map[string]string{"my-annotation-key": "my-annotation-val"} + updateCredentialIssuerInInformerAndWait(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: localhostIP, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + Annotations: annotations, + }, + }, + }, pinnipedInformers.Config().V1alpha1().CredentialIssuers()) + + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 5) // one more item to update the loadbalancer + clusterIPService = requireClusterIPWasUpdated(kubeAPIClient.Actions()[4]) + require.Equal(t, annotations, clusterIPService.Annotations) // now the annotations should exist on the load balancer + requireTLSServerIsRunning(ca, testServerAddr(), nil) + requireCredentialIssuer(newSuccessStrategy(localhostIP, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + when("requesting a load balancer via CredentialIssuer, then adding a static loadBalancerIP to the spec", func() { it.Before(func() { addSecretToTrackers(signingCASecret, kubeInformerClient) @@ -2838,6 +2913,65 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("there is an error creating the cluster ip", func() { + it.Before(func() { + addNodeWithRoleToTracker("worker", kubeAPIClient) + kubeAPIClient.PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, fmt.Errorf("error on create") + }) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + }) + + it("returns an error", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "error on create") + requireCredentialIssuer(newErrorStrategy("error on create")) + requireSigningCertProviderIsEmpty() + requireTLSServerIsRunningWithoutCerts() + }) + }) + + when("there is an error updating the cluster ip", func() { + it.Before(func() { + addNodeWithRoleToTracker("worker", kubeAPIClient) + kubeAPIClient.PrependReactor("update", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, fmt.Errorf("error on update") + }) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeAuto, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + Annotations: map[string]string{"key": "val"}, + }, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addClusterIPServiceToTracker(clusterIPServiceName, localhostIP, kubeAPIClient) + addClusterIPServiceToTracker(clusterIPServiceName, localhostIP, kubeInformerClient) + }) + + it("returns an error", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "error on update") + requireCredentialIssuer(newErrorStrategy("error on update")) + requireSigningCertProviderIsEmpty() + requireTLSServerIsRunningWithoutCerts() + }) + }) + when("there is an error creating the tls secret", func() { it.Before(func() { addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ From b4bb0db6e542a7728a74ae9415e1396bf7e1ed41 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Fri, 21 May 2021 09:57:46 -0700 Subject: [PATCH 22/43] Refactor some shared code between load balancer and cluster ip creation --- .../impersonatorconfig/impersonator_config.go | 116 ++++++------------ .../impersonator_config_test.go | 35 ++---- 2 files changed, 51 insertions(+), 100 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 63179559..92267b12 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -329,8 +329,8 @@ func (c *impersonatorConfigController) shouldHaveTLSSecret(config *v1alpha1.Impe return c.shouldHaveImpersonator(config) } -func (c *impersonatorConfigController) loadBalancerExists() (bool, error) { - _, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName) +func (c *impersonatorConfigController) serviceExists(serviceName string) (bool, error) { + _, err := c.servicesInformer.Lister().Services(c.namespace).Get(serviceName) notFound := k8serrors.IsNotFound(err) if notFound { return false, nil @@ -341,40 +341,16 @@ func (c *impersonatorConfigController) loadBalancerExists() (bool, error) { return true, nil } -func (c *impersonatorConfigController) clusterIPExists() (bool, error) { - _, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedClusterIPServiceName) - notFound := k8serrors.IsNotFound(err) - if notFound { - return false, nil - } - if err != nil { - return false, err - } - return true, nil -} - -func (c *impersonatorConfigController) loadBalancerNeedsUpdate(config *v1alpha1.ImpersonationProxySpec) (bool, error) { - lb, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName) +func (c *impersonatorConfigController) serviceNeedsUpdate(config *v1alpha1.ImpersonationProxySpec, serviceName string) (bool, error) { + service, err := c.servicesInformer.Lister().Services(c.namespace).Get(serviceName) if err != nil { return false, err } // TODO this will break if anything other than pinniped is adding annotations - if !reflect.DeepEqual(lb.Annotations, config.Service.Annotations) { + if !reflect.DeepEqual(service.Annotations, config.Service.Annotations) { return true, nil } - if lb.Spec.LoadBalancerIP != config.Service.LoadBalancerIP { - return true, nil - } - return false, nil -} - -func (c *impersonatorConfigController) ClusterIPNeedsUpdate(config *v1alpha1.ImpersonationProxySpec) (bool, error) { - clusterIP, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedClusterIPServiceName) - if err != nil { - return false, err - } - // TODO this will break if anything other than pinniped is adding annotations - if !reflect.DeepEqual(clusterIP.Annotations, config.Service.Annotations) { + if service.Spec.LoadBalancerIP != config.Service.LoadBalancerIP { return true, nil } return false, nil @@ -487,33 +463,11 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.C Annotations: config.Service.Annotations, }, } - running, err := c.loadBalancerExists() - if err != nil { - return err - } - if running { - needsUpdate, err := c.loadBalancerNeedsUpdate(config) - if err != nil { - return err - } - if needsUpdate { - plog.Info("updating load balancer for impersonation proxy", - "service", c.generatedLoadBalancerServiceName, - "namespace", c.namespace) - _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &loadBalancer, metav1.UpdateOptions{}) - return err - } - return nil - } - plog.Info("creating load balancer for impersonation proxy", - "service", c.generatedLoadBalancerServiceName, - "namespace", c.namespace) - _, err = c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, &loadBalancer, metav1.CreateOptions{}) - return err + return c.createOrUpdateService(ctx, config, &loadBalancer) } func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.Context) error { - running, err := c.loadBalancerExists() + running, err := c.serviceExists(c.generatedLoadBalancerServiceName) if err != nil { return err } @@ -548,33 +502,11 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx conte Annotations: config.Service.Annotations, }, } - running, err := c.clusterIPExists() - if err != nil { - return err - } - if running { - needsUpdate, err := c.ClusterIPNeedsUpdate(config) - if err != nil { - return err - } - if needsUpdate { - plog.Info("updating cluster ip for impersonation proxy", - "service", c.generatedLoadBalancerServiceName, - "namespace", c.namespace) - _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &clusterIP, metav1.UpdateOptions{}) - return err - } - return nil - } - plog.Info("creating cluster ip for impersonation proxy", - "service", c.generatedClusterIPServiceName, - "namespace", c.namespace) - _, err = c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, &clusterIP, metav1.CreateOptions{}) - return err + return c.createOrUpdateService(ctx, config, &clusterIP) } func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx context.Context) error { - running, err := c.clusterIPExists() + running, err := c.serviceExists(c.generatedClusterIPServiceName) if err != nil { return err } @@ -588,6 +520,34 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx conte return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{}) } +func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context, config *v1alpha1.ImpersonationProxySpec, service *v1.Service) error { + running, err := c.serviceExists(service.Name) + if err != nil { + return err + } + if running { + needsUpdate, err := c.serviceNeedsUpdate(config, service.Name) + if err != nil { + return err + } + if needsUpdate { + plog.Info("updating service for impersonation proxy", + "service type", service.Spec.Type, + "service", service.Name, + "namespace", c.namespace) + _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, service, metav1.UpdateOptions{}) + return err + } + return nil + } + plog.Info("creating service for impersonation proxy", + "service type", service.Spec.Type, + "service", service.Name, + "namespace", c.namespace) + _, err = c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, service, metav1.CreateOptions{}) + return err +} + func (c *impersonatorConfigController) ensureTLSSecret(ctx context.Context, nameInfo *certNameInfo, ca *certauthority.CA) error { secretFromInformer, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 2ed70277..696bcc90 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -874,6 +874,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Equal([]v1alpha1.CredentialIssuerStrategy{expectedStrategy}, credentialIssuer.Status.Strategies) } + var requireServiceWasDeleted = func(action coretesting.Action, serviceName string) { + deleteAction, ok := action.(coretesting.DeleteAction) + r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) + r.Equal("delete", deleteAction.GetVerb()) + r.Equal(serviceName, deleteAction.GetName()) + r.Equal("services", deleteAction.GetResource().Resource) + } + var requireLoadBalancerWasCreated = func(action coretesting.Action) *corev1.Service { createAction, ok := action.(coretesting.CreateAction) r.True(ok, "should have been able to cast this action to CreateAction: %v", action) @@ -887,14 +895,6 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { return createdLoadBalancerService } - var requireLoadBalancerWasDeleted = func(action coretesting.Action) { - deleteAction, ok := action.(coretesting.DeleteAction) - r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) - r.Equal("delete", deleteAction.GetVerb()) - r.Equal(loadBalancerServiceName, deleteAction.GetName()) - r.Equal("services", deleteAction.GetResource().Resource) - } - var requireLoadBalancerWasUpdated = func(action coretesting.Action) *corev1.Service { updateAction, ok := action.(coretesting.UpdateAction) r.True(ok, "should have been able to cast this action to UpdateAction: %v", action) @@ -920,15 +920,6 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { return createdClusterIPService } - var requireClusterIPWasDeleted = func(action coretesting.Action) { - // TODO maybe de-dup this with loadbalancerwasdeleted - deleteAction, ok := action.(coretesting.DeleteAction) - r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) - r.Equal("delete", deleteAction.GetVerb()) - r.Equal(clusterIPServiceName, deleteAction.GetName()) - r.Equal("services", deleteAction.GetResource().Resource) - } - var requireClusterIPWasUpdated = func(action coretesting.Action) *corev1.Service { updateAction, ok := action.(coretesting.UpdateAction) r.True(ok, "should have been able to cast this action to UpdateAction: %v", action) @@ -1162,7 +1153,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireTLSServerWasNeverStarted() r.Len(kubeAPIClient.Actions(), 3) requireNodesListed(kubeAPIClient.Actions()[0]) - requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[1]) + requireServiceWasDeleted(kubeAPIClient.Actions()[1], loadBalancerServiceName) requireTLSSecretWasDeleted(kubeAPIClient.Actions()[2]) requireCredentialIssuer(newAutoDisabledStrategy()) requireSigningCertProviderIsEmpty() @@ -2102,7 +2093,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(runControllerSync()) requireTLSServerIsNoLongerRunning() r.Len(kubeAPIClient.Actions(), 4) - requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[3]) + requireServiceWasDeleted(kubeAPIClient.Actions()[3], loadBalancerServiceName) requireCredentialIssuer(newManuallyDisabledStrategy()) requireSigningCertProviderIsEmpty() @@ -2168,7 +2159,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(runControllerSync()) requireTLSServerIsNoLongerRunning() r.Len(kubeAPIClient.Actions(), 4) - requireClusterIPWasDeleted(kubeAPIClient.Actions()[3]) + requireServiceWasDeleted(kubeAPIClient.Actions()[3], clusterIPServiceName) requireCredentialIssuer(newManuallyDisabledStrategy()) requireSigningCertProviderIsEmpty() @@ -2284,7 +2275,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 9) - requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[6]) + requireServiceWasDeleted(kubeAPIClient.Actions()[6], loadBalancerServiceName) requireTLSSecretWasDeleted(kubeAPIClient.Actions()[7]) requireTLSSecretWasCreated(kubeAPIClient.Actions()[8], ca) // recreated because the endpoint was updated, reused the old CA requireTLSServerIsRunning(ca, testServerAddr(), nil) @@ -3102,7 +3093,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireTLSServerWasNeverStarted() r.Len(kubeAPIClient.Actions(), 3) requireNodesListed(kubeAPIClient.Actions()[0]) - requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[1]) + requireServiceWasDeleted(kubeAPIClient.Actions()[1], loadBalancerServiceName) requireTLSSecretWasDeleted(kubeAPIClient.Actions()[2]) }) }) From 722aa72206157824fb3c686dc01549848a4089d1 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Fri, 21 May 2021 10:19:33 -0700 Subject: [PATCH 23/43] Integration test tests update functionality --- .../concierge_impersonation_proxy_test.go | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index ff5ebd9e..a0397f89 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -21,6 +21,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "strings" "sync" "testing" @@ -196,8 +197,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ Mode: conciergev1alpha.ImpersonationProxyModeAuto, Service: conciergev1alpha.ImpersonationProxyServiceSpec{ - Type: conciergev1alpha.ImpersonationProxyServiceTypeLoadBalancer, - Annotations: map[string]string{"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "4000"}, + Type: conciergev1alpha.ImpersonationProxyServiceTypeLoadBalancer, }, }, }) @@ -377,6 +377,25 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl t.Parallel() kubeconfigPath, envVarsWithProxy, _ := getImpersonationKubeconfig(t, env, impersonationProxyURL, impersonationProxyCACertPEM, credentialRequestSpecWithWorkingCredentials.Authenticator) + // set credential issuer to have new annotation to ensure the idle timeout is long enough on AWS. + // this also ensures that updates to the credential issuer impersonation proxy spec go through + if impersonatorShouldHaveStartedAutomaticallyByDefault && clusterSupportsLoadBalancers { + // configure the credential issuer spec to have the impersonation proxy in auto mode + updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ + ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ + Mode: conciergev1alpha.ImpersonationProxyModeAuto, + Service: conciergev1alpha.ImpersonationProxyServiceSpec{ + Type: conciergev1alpha.ImpersonationProxyServiceTypeLoadBalancer, + Annotations: map[string]string{"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "4000"}, + }, + }, + }) + // Wait until the annotation shows up on the load balancer + library.RequireEventuallyWithoutError(t, func() (bool, error) { + return loadBalancerHasAnnotations(ctx, env, adminClient, map[string]string{"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "4000"}) + }, 30*time.Second, 500*time.Millisecond) + } + // Run the kubectl port-forward command. timeout, cancelFunc := context.WithTimeout(ctx, 2*time.Minute) defer cancelFunc() @@ -1472,6 +1491,18 @@ func hasImpersonationProxyLoadBalancerService(ctx context.Context, env *library. return service.Spec.Type == corev1.ServiceTypeLoadBalancer, nil } +func loadBalancerHasAnnotations(ctx context.Context, env *library.TestEnv, client kubernetes.Interface, annotations map[string]string) (bool, error) { + service, err := client.CoreV1().Services(env.ConciergeNamespace).Get(ctx, impersonationProxyLoadBalancerName(env), metav1.GetOptions{}) + if k8serrors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + hasExactAnnotations := reflect.DeepEqual(annotations, service.Annotations) + return service.Spec.Type == corev1.ServiceTypeLoadBalancer && hasExactAnnotations, nil +} + func impersonationProxyTLSSecretName(env *library.TestEnv) string { return env.ConciergeAppName + "-impersonation-proxy-tls-serving-certificate" } From 150e879a683955ed54d5c5c76b0ea9ddbb51f164 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Fri, 21 May 2021 13:47:06 -0700 Subject: [PATCH 24/43] Add tests for deleting services --- .../impersonator_config_test.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 696bcc90..fc8e346b 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -2904,6 +2904,32 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("there is an error deleting the load balancer", func() { + it.Before(func() { + addNodeWithRoleToTracker("worker", kubeAPIClient) + kubeAPIClient.PrependReactor("delete", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, fmt.Errorf("error on delete") + }) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient) + addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient) + }) + + it("returns an error", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "error on delete") + requireCredentialIssuer(newErrorStrategy("error on delete")) + requireSigningCertProviderIsEmpty() + }) + }) + when("there is an error creating the cluster ip", func() { it.Before(func() { addNodeWithRoleToTracker("worker", kubeAPIClient) @@ -2963,6 +2989,32 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("there is an error deleting the cluster ip", func() { + it.Before(func() { + addNodeWithRoleToTracker("worker", kubeAPIClient) + kubeAPIClient.PrependReactor("delete", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, fmt.Errorf("error on delete") + }) + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeDisabled, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addClusterIPServiceToTracker(clusterIPServiceName, localhostIP, kubeAPIClient) + addClusterIPServiceToTracker(clusterIPServiceName, localhostIP, kubeInformerClient) + }) + + it("returns an error", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "error on delete") + requireCredentialIssuer(newErrorStrategy("error on delete")) + requireSigningCertProviderIsEmpty() + }) + }) + when("there is an error creating the tls secret", func() { it.Before(func() { addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ From 5de9bac4ac4280affd96bdc7ae529bff21b0ef3c Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Mon, 24 May 2021 09:41:49 -0700 Subject: [PATCH 25/43] Oof... good I wrote an integration test because that's not how updating works! Now updating the existing service in kubernetes but with the new annotations --- .../impersonatorconfig/impersonator_config.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 92267b12..3d3bcd3c 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -341,19 +341,19 @@ func (c *impersonatorConfigController) serviceExists(serviceName string) (bool, return true, nil } -func (c *impersonatorConfigController) serviceNeedsUpdate(config *v1alpha1.ImpersonationProxySpec, serviceName string) (bool, error) { +func (c *impersonatorConfigController) serviceNeedsUpdate(config *v1alpha1.ImpersonationProxySpec, serviceName string) (*v1.Service, bool, error) { service, err := c.servicesInformer.Lister().Services(c.namespace).Get(serviceName) if err != nil { - return false, err + return nil, false, err } // TODO this will break if anything other than pinniped is adding annotations if !reflect.DeepEqual(service.Annotations, config.Service.Annotations) { - return true, nil + return service, true, nil } if service.Spec.LoadBalancerIP != config.Service.LoadBalancerIP { - return true, nil + return service, true, nil } - return false, nil + return nil, false, nil } func (c *impersonatorConfigController) tlsSecretExists() (bool, *v1.Secret, error) { @@ -526,7 +526,7 @@ func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context return err } if running { - needsUpdate, err := c.serviceNeedsUpdate(config, service.Name) + existingService, needsUpdate, err := c.serviceNeedsUpdate(config, service.Name) if err != nil { return err } @@ -535,7 +535,12 @@ func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context "service type", service.Spec.Type, "service", service.Name, "namespace", c.namespace) - _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, service, metav1.UpdateOptions{}) + // update only the annotation and loadBalancerIP fields on the service + var newService = &v1.Service{} + existingService.DeepCopyInto(newService) + newService.Annotations = service.Annotations + newService.Spec.LoadBalancerIP = service.Spec.LoadBalancerIP + _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, newService, metav1.UpdateOptions{}) return err } return nil From 75dd98a9653907e40c7bbc9355c6c206e48edeb4 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Tue, 25 May 2021 13:50:50 -0700 Subject: [PATCH 26/43] Integration test for impersonation proxy cluster ip --- .../concierge_impersonation_proxy_test.go | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index a0397f89..dff36ac0 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -380,7 +380,6 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // set credential issuer to have new annotation to ensure the idle timeout is long enough on AWS. // this also ensures that updates to the credential issuer impersonation proxy spec go through if impersonatorShouldHaveStartedAutomaticallyByDefault && clusterSupportsLoadBalancers { - // configure the credential issuer spec to have the impersonation proxy in auto mode updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ Mode: conciergev1alpha.ImpersonationProxyModeAuto, @@ -1201,6 +1200,40 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl }) }) + t.Run("running impersonation proxy with ClusterIP service", func(t *testing.T) { + if env.Proxy == "" { + t.Skip("Skipping ClusterIP test because squid proxy is not present") + } + clusterIPServiceURL := fmt.Sprintf("%s.%s.svc.cluster.local", impersonationProxyClusterIPName(env), env.ConciergeNamespace) + updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ + ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ + Mode: conciergev1alpha.ImpersonationProxyModeEnabled, + ExternalEndpoint: clusterIPServiceURL, + Service: conciergev1alpha.ImpersonationProxyServiceSpec{ + Type: conciergev1alpha.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }) + // Wait until the clusterip exists + library.RequireEventuallyWithoutError(t, func() (bool, error) { + return hasImpersonationProxyClusterIPService(ctx, env, adminClient) + }, 30*time.Second, 500*time.Millisecond) + + newImpersonationProxyURL, newImpersonationProxyCACertPEM := performImpersonatorDiscovery(ctx, t, env, adminConciergeClient) + + refreshedCredentials := refreshCredential(t, impersonationProxyURL, impersonationProxyCACertPEM).DeepCopy() + kubeconfig := impersonationProxyRestConfig(refreshedCredentials, newImpersonationProxyURL, newImpersonationProxyCACertPEM, nil) + kubeconfig.Proxy = kubeconfigProxyFunc(t, env.Proxy) + + client := library.NewKubeclient(t, kubeconfig).Kubernetes + + // everything should work properly through the cluster ip service + t.Run( + "access as user", + library.AccessAsUserTest(ctx, env.TestUser.ExpectedUsername, client), + ) + }) + t.Run("manually disabling the impersonation proxy feature", func(t *testing.T) { // Update configuration to force the proxy to disabled mode updateCredentialIssuer(ctx, t, env, adminConciergeClient, conciergev1alpha.CredentialIssuerSpec{ @@ -1503,6 +1536,17 @@ func loadBalancerHasAnnotations(ctx context.Context, env *library.TestEnv, clien return service.Spec.Type == corev1.ServiceTypeLoadBalancer && hasExactAnnotations, nil } +func hasImpersonationProxyClusterIPService(ctx context.Context, env *library.TestEnv, client kubernetes.Interface) (bool, error) { + service, err := client.CoreV1().Services(env.ConciergeNamespace).Get(ctx, impersonationProxyClusterIPName(env), metav1.GetOptions{}) + if k8serrors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + return service.Spec.Type == corev1.ServiceTypeClusterIP, nil +} + func impersonationProxyTLSSecretName(env *library.TestEnv) string { return env.ConciergeAppName + "-impersonation-proxy-tls-serving-certificate" } @@ -1515,6 +1559,10 @@ func impersonationProxyLoadBalancerName(env *library.TestEnv) string { return env.ConciergeAppName + "-impersonation-proxy-load-balancer" } +func impersonationProxyClusterIPName(env *library.TestEnv) string { + return env.ConciergeAppName + "-impersonation-proxy-cluster-ip" +} + func credentialIssuerName(env *library.TestEnv) string { return env.ConciergeAppName + "-config" } From 450ce6a4aae63f24cb1e78adda5c91876faf6042 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Tue, 25 May 2021 17:44:25 -0500 Subject: [PATCH 27/43] Switch impersonatorconfig to new endpointaddr package. Signed-off-by: Matt Moyer --- .../impersonatorconfig/impersonator_config.go | 57 ++++--------------- .../impersonator_config_test.go | 50 ---------------- 2 files changed, 11 insertions(+), 96 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 3d3bcd3c..ffabd619 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -12,7 +12,6 @@ import ( "fmt" "net" "reflect" - "strconv" "strings" "time" @@ -41,6 +40,7 @@ import ( "go.pinniped.dev/internal/controller/issuerconfig" "go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/dynamiccert" + "go.pinniped.dev/internal/endpointaddr" "go.pinniped.dev/internal/plog" ) @@ -761,13 +761,13 @@ func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1a } func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig(config *v1alpha1.ImpersonationProxySpec) *certNameInfo { - endpointMaybeWithPort := config.ExternalEndpoint - endpointWithoutPort := strings.Split(endpointMaybeWithPort, ":")[0] - parsedAsIP := net.ParseIP(endpointWithoutPort) - if parsedAsIP != nil { - return &certNameInfo{ready: true, selectedIP: parsedAsIP, clientEndpoint: endpointMaybeWithPort} + addr, _ := endpointaddr.Parse(config.ExternalEndpoint, 443) + endpoint := strings.TrimSuffix(addr.Endpoint(), ":443") + + if ip := net.ParseIP(addr.Host); ip != nil { + return &certNameInfo{ready: true, selectedIP: ip, clientEndpoint: endpoint} } - return &certNameInfo{ready: true, selectedHostname: endpointWithoutPort, clientEndpoint: endpointMaybeWithPort} + return &certNameInfo{ready: true, selectedHostname: addr.Host, clientEndpoint: endpoint} } func (c *impersonatorConfigController) findTLSCertificateNameFromLoadBalancer() (*certNameInfo, error) { @@ -1017,46 +1017,11 @@ func validateCredentialIssuerSpec(spec *v1alpha1.ImpersonationProxySpec) error { return fmt.Errorf("externalEndpoint must be set when service.type is None") } - if err := validateExternalEndpoint(spec.ExternalEndpoint); err != nil { - return fmt.Errorf("invalid ExternalEndpoint %q: %w", spec.ExternalEndpoint, err) + if spec.ExternalEndpoint != "" { + if _, err := endpointaddr.Parse(spec.ExternalEndpoint, 443); err != nil { + return fmt.Errorf("invalid ExternalEndpoint %q: %w", spec.ExternalEndpoint, err) + } } return nil } - -func validateExternalEndpoint(endpoint string) error { - // Empty string is valid (no external endpoint, default to service name) - if endpoint == "" { - return nil - } - - // Try parsing it both with and without an implicit port 443 at the end. - host, port, err := net.SplitHostPort(endpoint) - - // If we got an error parsing the raw input, try adding an implicit port 443. - if err != nil { - host, port, err = net.SplitHostPort(net.JoinHostPort(endpoint, "443")) - } - - // If there's still an error, fail now. - if err != nil { - return err - } - - portInt, _ := strconv.Atoi(port) - if len(validation.IsValidPortNum(portInt)) > 0 { - return fmt.Errorf("invalid port %q", port) - } - - // Check if the host part is a valid IP address. - if len(validation.IsValidIP(host)) == 0 { - return nil - } - - // Check if the host part is a valid hostname according to RFC 1123. - if len(validation.IsDNS1123Subdomain(host)) == 0 { - return nil - } - - return fmt.Errorf("host %q is not a valid hostname or IP address", host) -} diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index fc8e346b..6f0c0fa1 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -3535,53 +3535,3 @@ func (q *testQueue) AddRateLimited(key controllerlib.Key) { q.key = key } - -func TestValidateExternalEndpoint(t *testing.T) { - t.Parallel() - - for _, tt := range []struct { - input string - expectErr string - }{ - {input: ""}, - {input: "127.0.0.1"}, - {input: "127.0.0.1:8443"}, - {input: "[127.0.0.1]:8443"}, - {input: "2001:db8::ffff"}, - {input: "[2001:db8::ffff]:8443"}, - {input: "host.example.com"}, - {input: "host-dev.example.com"}, - {input: "host.example.com:8443"}, - {input: "[host.example.com]:8443"}, - { - input: "https://host.example.com", - expectErr: `invalid port "//host.example.com"`, - }, - { - input: "host.example.com/some/path", - expectErr: `host "host.example.com/some/path" is not a valid hostname or IP address`, - }, - { - input: "[host.example.com", - expectErr: "address [host.example.com:443: missing ']' in address", - }, - { - input: "___.example.com:1234", - expectErr: `host "___.example.com" is not a valid hostname or IP address`, - }, - { - input: "HOST.EXAMPLE.COM", - expectErr: `host "HOST.EXAMPLE.COM" is not a valid hostname or IP address`, - }, - } { - tt := tt - t.Run(fmt.Sprintf("parse %q", tt.input), func(t *testing.T) { - got := validateExternalEndpoint(tt.input) - if tt.expectErr == "" { - require.NoError(t, got) - } else { - require.EqualError(t, got, tt.expectErr) - } - }) - } -} From e2fad6932f09a39802dff717d5544c8f885872bf Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Tue, 25 May 2021 17:01:42 -0700 Subject: [PATCH 28/43] multiple cluster ips --- .../impersonatorconfig/impersonator_config.go | 46 ++++++++++--------- .../impersonator_config_test.go | 44 +++++++++++++++++- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 3d3bcd3c..2e9b1861 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -193,7 +193,7 @@ type certNameInfo struct { // The IP address or hostname which was selected to be used as the name in the cert. // Either selectedIP or selectedHostname will be set, but not both. - selectedIP net.IP + selectedIPs []net.IP selectedHostname string // The name of the endpoint to which a client should connect to talk to the impersonator. @@ -636,14 +636,14 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc actualIPs := actualCertFromSecret.IPAddresses actualHostnames := actualCertFromSecret.DNSNames plog.Info("Checking TLS certificate names", - "desiredIP", nameInfo.selectedIP, + "desiredIPs", nameInfo.selectedIPs, "desiredHostname", nameInfo.selectedHostname, "actualIPs", actualIPs, "actualHostnames", actualHostnames, "secret", c.tlsSecretName, "namespace", c.namespace) - if certHostnameAndIPMatchDesiredState(nameInfo.selectedIP, actualIPs, nameInfo.selectedHostname, actualHostnames) { + if certHostnameAndIPMatchDesiredState(nameInfo.selectedIPs, actualIPs, nameInfo.selectedHostname, actualHostnames) { // The cert already matches the desired state, so there is no need to delete/recreate it. return false, nil } @@ -654,8 +654,13 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc return true, nil } -func certHostnameAndIPMatchDesiredState(desiredIP net.IP, actualIPs []net.IP, desiredHostname string, actualHostnames []string) bool { - if desiredIP != nil && len(actualIPs) == 1 && desiredIP.Equal(actualIPs[0]) && len(actualHostnames) == 0 { +func certHostnameAndIPMatchDesiredState(desiredIPs []net.IP, actualIPs []net.IP, desiredHostname string, actualHostnames []string) bool { + if len(desiredIPs) > 0 && len(actualIPs) > 0 && len(actualIPs) == len(desiredIPs) && len(actualHostnames) == 0 { + for i := range desiredIPs { + if !actualIPs[i].Equal(desiredIPs[i]) { + return false + } + } return true } if desiredHostname != "" && len(actualHostnames) == 1 && desiredHostname == actualHostnames[0] && len(actualIPs) == 0 { @@ -677,7 +682,7 @@ func (c *impersonatorConfigController) ensureTLSSecretIsCreatedAndLoaded(ctx con return nil } - newTLSSecret, err := c.createNewTLSSecret(ctx, ca, nameInfo.selectedIP, nameInfo.selectedHostname) + newTLSSecret, err := c.createNewTLSSecret(ctx, ca, nameInfo.selectedIPs, nameInfo.selectedHostname) if err != nil { return err } @@ -746,12 +751,6 @@ func (c *impersonatorConfigController) createCASecret(ctx context.Context) (*cer } func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1alpha1.ImpersonationProxySpec) (*certNameInfo, error) { - // possible valid options: - // - you have a loadbalancer and are autoconfiguring the endpoint -> get cert info based on load balancer ip/hostnome - // - you have a loadbalancer AND an external endpoint -> either should work since they should be the same - // - external endpoint no loadbalancer or other service -> use the endpoint config - // - external endpoint and ClusterIP -> use external endpoint? - // - clusterip and no external endpoint if config.ExternalEndpoint != "" { return c.findTLSCertificateNameFromEndpointConfig(config), nil } else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { @@ -765,7 +764,7 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig( endpointWithoutPort := strings.Split(endpointMaybeWithPort, ":")[0] parsedAsIP := net.ParseIP(endpointWithoutPort) if parsedAsIP != nil { - return &certNameInfo{ready: true, selectedIP: parsedAsIP, clientEndpoint: endpointMaybeWithPort} + return &certNameInfo{ready: true, selectedIPs: []net.IP{parsedAsIP}, clientEndpoint: endpointMaybeWithPort} } return &certNameInfo{ready: true, selectedHostname: endpointWithoutPort, clientEndpoint: endpointMaybeWithPort} } @@ -797,7 +796,7 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromLoadBalancer() ip := ingress.IP parsedIP := net.ParseIP(ip) if parsedIP != nil { - return &certNameInfo{ready: true, selectedIP: parsedIP, clientEndpoint: ip}, nil + return &certNameInfo{ready: true, selectedIPs: []net.IP{parsedIP}, clientEndpoint: ip}, nil } } @@ -815,22 +814,27 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromClusterIPServic return nil, err } ip := clusterIP.Spec.ClusterIP + ips := clusterIP.Spec.ClusterIPs if ip != "" { - parsedIP := net.ParseIP(ip) - return &certNameInfo{ready: true, selectedIP: parsedIP, clientEndpoint: ip}, nil + // clusterIP will always exist when clusterIPs does, but not vice versa + var parsedIPs []net.IP + if len(ips) > 0 { + for _, ipFromIPs := range ips { + parsedIPs = append(parsedIPs, net.ParseIP(ipFromIPs)) + } + } else { + parsedIPs = []net.IP{net.ParseIP(ip)} + } + return &certNameInfo{ready: true, selectedIPs: parsedIPs, clientEndpoint: ip}, nil } return &certNameInfo{ready: false}, nil } -func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, ca *certauthority.CA, ip net.IP, hostname string) (*v1.Secret, error) { +func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, ca *certauthority.CA, ips []net.IP, hostname string) (*v1.Secret, error) { var hostnames []string - var ips []net.IP if hostname != "" { hostnames = []string{hostname} } - if ip != nil { - ips = []net.IP{ip} - } impersonationCert, err := ca.IssueServerCert(hostnames, ips, approximatelyOneHundredYears) if err != nil { diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index fc8e346b..f188a4cf 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -427,7 +427,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { t.Logf("DialContext replacing addr %s with %s", addr, replacementAddr) addr = replacementAddr } else if dnsOverrides != nil { - t.Fatal("dnsOverrides was provided but not used, which was probably a mistake") + t.Fatalf("dnsOverrides was provided but not used, which was probably a mistake. addr %s", addr) } return realDialer.DialContext(ctx, network, addr) } @@ -747,6 +747,15 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(client.Tracker().Add(clusterIPService)) } + var addDualStackClusterIPServiceToTracker = func(resourceName string, clusterIP0 string, clusterIP1 string, client *kubernetesfake.Clientset) { + clusterIPService := newClusterIPService(resourceName, corev1.ServiceStatus{}, corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + ClusterIP: clusterIP0, + ClusterIPs: []string{clusterIP0, clusterIP1}, + }) + r.NoError(client.Tracker().Add(clusterIPService)) + } + var addSecretToTrackers = func(secret *corev1.Secret, clients ...*kubernetesfake.Clientset) { for _, client := range clients { secretCopy := secret.DeepCopy() @@ -1541,6 +1550,39 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("a clusterip service exists with dual stack ips", func() { + const fakeIP1 = "127.0.0.123" + const fakeIP2 = "127.0.0.234" // TODO test that this works for an IPv6 address, which is the actual use case for multiple clusterips + it.Before(func() { + addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, + Spec: v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, + }, pinnipedInformerClient, pinnipedAPIClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + addDualStackClusterIPServiceToTracker(clusterIPServiceName, fakeIP1, fakeIP2, kubeInformerClient) + addDualStackClusterIPServiceToTracker(clusterIPServiceName, fakeIP1, fakeIP2, kubeAPIClient) + }) + + it("certs are valid for both ip addresses", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) + requireTLSServerIsRunning(ca, fakeIP2, map[string]string{fakeIP2 + ":443": testServerAddr()}) + requireTLSServerIsRunning(ca, fakeIP1, map[string]string{fakeIP1 + ":443": testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeIP1, ca)) + }) + }) + when("a load balancer and a secret already exists", func() { var caCrt []byte it.Before(func() { From e5a61f3b95cd4753f38c38a97572e55380453404 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Wed, 26 May 2021 10:30:33 -0700 Subject: [PATCH 29/43] IPv6 address in unit tests for ClusterIPs --- .../controller/impersonatorconfig/impersonator_config_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index ec4840a9..8abe0999 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -1552,7 +1552,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { when("a clusterip service exists with dual stack ips", func() { const fakeIP1 = "127.0.0.123" - const fakeIP2 = "127.0.0.234" // TODO test that this works for an IPv6 address, which is the actual use case for multiple clusterips + const fakeIP2 = "fd00::5118" it.Before(func() { addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, @@ -1577,7 +1577,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireNodesListed(kubeAPIClient.Actions()[0]) ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca) - requireTLSServerIsRunning(ca, fakeIP2, map[string]string{fakeIP2 + ":443": testServerAddr()}) + requireTLSServerIsRunning(ca, "["+fakeIP2+"]", map[string]string{"[fd00::5118]:443": testServerAddr()}) requireTLSServerIsRunning(ca, fakeIP1, map[string]string{fakeIP1 + ":443": testServerAddr()}) requireCredentialIssuer(newSuccessStrategy(fakeIP1, ca)) }) From b13c494f932c1fee5a7094d922d444d733f47295 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 26 May 2021 12:42:50 -0500 Subject: [PATCH 30/43] Migrate off global logger in impersonatorconfig. Signed-off-by: Matt Moyer --- .../impersonatorconfig/impersonator_config.go | 114 +++++++++--------- .../impersonator_config_test.go | 7 ++ .../controllermanager/prepare_controllers.go | 1 + 3 files changed, 68 insertions(+), 54 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index fbd55608..6af92288 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation" corev1informers "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" pinnipedclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned" @@ -41,7 +43,6 @@ import ( "go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/dynamiccert" "go.pinniped.dev/internal/endpointaddr" - "go.pinniped.dev/internal/plog" ) const ( @@ -79,6 +80,8 @@ type impersonatorConfigController struct { serverStopCh chan struct{} errorCh chan error tlsServingCertDynamicCertProvider dynamiccert.Private + infoLog logr.Logger + debugLog logr.Logger } func NewImpersonatorConfigController( @@ -100,8 +103,10 @@ func NewImpersonatorConfigController( impersonatorFunc impersonator.FactoryFunc, impersonationSignerSecretName string, impersonationSigningCertProvider dynamiccert.Provider, + log logr.Logger, ) controllerlib.Controller { secretNames := sets.NewString(tlsSecretName, caSecretName, impersonationSignerSecretName) + log = log.WithName("impersonator-config-controller") return controllerlib.New( controllerlib.Config{ Name: "impersonator-config-controller", @@ -123,6 +128,8 @@ func NewImpersonatorConfigController( impersonationSigningCertProvider: impersonationSigningCertProvider, impersonatorFunc: impersonatorFunc, tlsServingCertDynamicCertProvider: dynamiccert.NewServingCert("impersonation-proxy-serving-cert"), + infoLog: log, + debugLog: log.V(10), }, }, withInformer(credentialIssuerInformer, @@ -151,7 +158,7 @@ func NewImpersonatorConfigController( } func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error { - plog.Debug("Starting impersonatorConfigController Sync") + c.debugLog.Info("starting impersonatorConfigController Sync") // Load the CredentialIssuer that we'll update with status. credIssuer, err := c.credIssuerInformer.Lister().Get(c.credentialIssuerResourceName) @@ -180,7 +187,7 @@ func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error )}) if err == nil { - plog.Debug("Successfully finished impersonatorConfigController Sync") + c.debugLog.Info("successfully finished impersonatorConfigController Sync") } return err } @@ -219,7 +226,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context, cre return nil, err } c.hasControlPlaneNodes = &hasControlPlaneNodes - plog.Debug("Queried for control plane nodes", "foundControlPlaneNodes", hasControlPlaneNodes) + c.debugLog.Info("queried for control plane nodes", "foundControlPlaneNodes", hasControlPlaneNodes) } if c.shouldHaveImpersonator(impersonationSpec) { @@ -295,9 +302,7 @@ func (c *impersonatorConfigController) loadImpersonationProxyConfiguration(credI if err := validateCredentialIssuerSpec(spec); err != nil { return nil, fmt.Errorf("could not load CredentialIssuer spec.impersonationProxy: %w", err) } - plog.Info("Read impersonation proxy config", - "credentialIssuer", c.credentialIssuerResourceName, - ) + c.infoLog.Info("read impersonation proxy config", "credentialIssuer", c.credentialIssuerResourceName) return spec, nil } @@ -392,7 +397,7 @@ func (c *impersonatorConfigController) ensureImpersonatorIsStarted(syncCtx contr } } - plog.Info("Starting impersonation proxy", "port", impersonationProxyPort) + c.infoLog.Info("starting impersonation proxy", "port", impersonationProxyPort) startImpersonatorFunc, err := c.impersonatorFunc( impersonationProxyPort, c.tlsServingCertDynamicCertProvider, @@ -427,7 +432,7 @@ func (c *impersonatorConfigController) ensureImpersonatorIsStopped(shouldCloseEr return nil } - plog.Info("Stopping impersonation proxy", "port", impersonationProxyPort) + c.infoLog.Info("stopping impersonation proxy", "port", impersonationProxyPort) close(c.serverStopCh) stopErr := <-c.errorCh @@ -475,9 +480,9 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.C return nil } - plog.Info("Deleting load balancer for impersonation proxy", - "service", c.generatedLoadBalancerServiceName, - "namespace", c.namespace) + c.infoLog.Info("deleting load balancer for impersonation proxy", + "service", klog.KRef(c.namespace, c.generatedLoadBalancerServiceName), + ) return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{}) } @@ -514,9 +519,9 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx conte return nil } - plog.Info("Deleting cluster ip for impersonation proxy", - "service", c.generatedClusterIPServiceName, - "namespace", c.namespace) + c.infoLog.Info("deleting cluster ip for impersonation proxy", + "service", klog.KRef(c.namespace, c.generatedClusterIPServiceName), + ) return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{}) } @@ -531,10 +536,10 @@ func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context return err } if needsUpdate { - plog.Info("updating service for impersonation proxy", - "service type", service.Spec.Type, - "service", service.Name, - "namespace", c.namespace) + c.infoLog.Info("updating service for impersonation proxy", + "serviceType", service.Spec.Type, + "service", klog.KObj(service), + ) // update only the annotation and loadBalancerIP fields on the service var newService = &v1.Service{} existingService.DeepCopyInto(newService) @@ -545,10 +550,10 @@ func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context } return nil } - plog.Info("creating service for impersonation proxy", - "service type", service.Spec.Type, - "service", service.Name, - "namespace", c.namespace) + c.infoLog.Info("creating service for impersonation proxy", + "serviceType", service.Spec.Type, + "service", klog.KObj(service), + ) _, err = c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, service, metav1.CreateOptions{}) return err } @@ -579,10 +584,10 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc certPEM := secret.Data[v1.TLSCertKey] block, _ := pem.Decode(certPEM) if block == nil { - plog.Warning("Found missing or not PEM-encoded data in TLS Secret", + c.infoLog.Info("found missing or not PEM-encoded data in TLS Secret", "invalidCertPEM", string(certPEM), - "secret", c.tlsSecretName, - "namespace", c.namespace) + "secret", klog.KObj(secret), + ) deleteErr := c.ensureTLSSecretIsRemoved(ctx) if deleteErr != nil { return false, fmt.Errorf("found missing or not PEM-encoded data in TLS Secret, but got error while deleting it: %w", deleteErr) @@ -592,10 +597,10 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc actualCertFromSecret, err := x509.ParseCertificate(block.Bytes) if err != nil { - plog.Error("Found invalid PEM data in TLS Secret", err, + c.infoLog.Error(err, "found missing or not PEM-encoded data in TLS Secret", "invalidCertPEM", string(certPEM), - "secret", c.tlsSecretName, - "namespace", c.namespace) + "secret", klog.KObj(secret), + ) if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return false, fmt.Errorf("PEM data represented an invalid cert, but got error while deleting it: %w", err) } @@ -605,9 +610,10 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc keyPEM := secret.Data[v1.TLSPrivateKeyKey] _, err = tls.X509KeyPair(certPEM, keyPEM) if err != nil { - plog.Error("Found invalid private key PEM data in TLS Secret", err, - "secret", c.tlsSecretName, - "namespace", c.namespace) + c.infoLog.Error(err, "found invalid private key PEM data in TLS Secret", + "invalidCertPEM", string(certPEM), + "secret", klog.KObj(secret), + ) if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return false, fmt.Errorf("cert had an invalid private key, but got error while deleting it: %w", err) } @@ -635,13 +641,13 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc actualIPs := actualCertFromSecret.IPAddresses actualHostnames := actualCertFromSecret.DNSNames - plog.Info("Checking TLS certificate names", + c.infoLog.Info("checking TLS certificate names", "desiredIPs", nameInfo.selectedIPs, "desiredHostname", nameInfo.selectedHostname, "actualIPs", actualIPs, "actualHostnames", actualHostnames, - "secret", c.tlsSecretName, - "namespace", c.namespace) + "secret", klog.KObj(secret), + ) if certHostnameAndIPMatchDesiredState(nameInfo.selectedIPs, actualIPs, nameInfo.selectedHostname, actualHostnames) { // The cert already matches the desired state, so there is no need to delete/recreate it. @@ -740,9 +746,9 @@ func (c *impersonatorConfigController) createCASecret(ctx context.Context) (*cer Type: v1.SecretTypeOpaque, } - plog.Info("Creating CA certificates for impersonation proxy", - "secret", c.caSecretName, - "namespace", c.namespace) + c.infoLog.Info("creating CA certificates for impersonation proxy", + "secret", klog.KObj(&secret), + ) if _, err = c.k8sClient.CoreV1().Secrets(c.namespace).Create(ctx, &secret, metav1.CreateOptions{}); err != nil { return nil, err } @@ -781,9 +787,9 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromLoadBalancer() } ingresses := lb.Status.LoadBalancer.Ingress if len(ingresses) == 0 || (ingresses[0].Hostname == "" && ingresses[0].IP == "") { - plog.Info("load balancer for impersonation proxy does not have an ingress yet, so skipping tls cert generation while we wait", - "service", c.generatedLoadBalancerServiceName, - "namespace", c.namespace) + c.infoLog.Info("load balancer for impersonation proxy does not have an ingress yet, so skipping tls cert generation while we wait", + "service", klog.KObj(lb), + ) return &certNameInfo{ready: false}, nil } for _, ingress := range ingresses { @@ -859,11 +865,11 @@ func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, c Type: v1.SecretTypeTLS, } - plog.Info("Creating TLS certificates for impersonation proxy", + c.infoLog.Info("creating TLS certificates for impersonation proxy", "ips", ips, "hostnames", hostnames, - "secret", c.tlsSecretName, - "namespace", c.namespace) + "secret", klog.KObj(newTLSSecret), + ) return c.k8sClient.CoreV1().Secrets(c.namespace).Create(ctx, newTLSSecret, metav1.CreateOptions{}) } @@ -876,10 +882,10 @@ func (c *impersonatorConfigController) loadTLSCertFromSecret(tlsSecret *v1.Secre return fmt.Errorf("could not parse TLS cert PEM data from Secret: %w", err) } - plog.Info("Loading TLS certificates for impersonation proxy", + c.infoLog.Info("loading TLS certificates for impersonation proxy", "certPEM", string(certPEM), - "secret", c.tlsSecretName, - "namespace", c.namespace) + "secret", klog.KObj(tlsSecret), + ) return nil } @@ -892,9 +898,9 @@ func (c *impersonatorConfigController) ensureTLSSecretIsRemoved(ctx context.Cont if !tlsSecretExists { return nil } - plog.Info("Deleting TLS certificates for impersonation proxy", - "secret", c.tlsSecretName, - "namespace", c.namespace) + c.infoLog.Info("deleting TLS certificates for impersonation proxy", + "secret", klog.KRef(c.namespace, c.tlsSecretName), + ) err = c.k8sClient.CoreV1().Secrets(c.namespace).Delete(ctx, c.tlsSecretName, metav1.DeleteOptions{}) notFound := k8serrors.IsNotFound(err) if notFound { @@ -930,16 +936,16 @@ func (c *impersonatorConfigController) loadSignerCA(status v1alpha1.StrategyStat return fmt.Errorf("could not load the impersonator's credential signing secret: %w", err) } - plog.Info("Loading credential signing certificate for impersonation proxy", + c.infoLog.Info("loading credential signing certificate for impersonation proxy", "certPEM", string(certPEM), - "fromSecret", c.impersonationSignerSecretName, - "namespace", c.namespace) + "secret", klog.KObj(signingCertSecret), + ) return nil } func (c *impersonatorConfigController) clearSignerCA() { - plog.Info("Clearing credential signing certificate for impersonation proxy") + c.infoLog.Info("clearing credential signing certificate for impersonation proxy") c.impersonationSigningCertProvider.UnsetCertKeyContent() } diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 8abe0999..c9dbe992 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -42,6 +42,7 @@ import ( "go.pinniped.dev/internal/dynamiccert" "go.pinniped.dev/internal/kubeclient" "go.pinniped.dev/internal/testutil" + "go.pinniped.dev/internal/testutil/testlogger" ) func TestImpersonatorConfigControllerOptions(t *testing.T) { @@ -60,6 +61,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { var credIssuerInformerFilter controllerlib.Filter var servicesInformerFilter controllerlib.Filter var secretsInformerFilter controllerlib.Filter + var testLog *testlogger.Logger it.Before(func() { r = require.New(t) @@ -70,6 +72,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { credIssuerInformer := pinnipedInformerFactory.Config().V1alpha1().CredentialIssuers() servicesInformer := sharedInformerFactory.Core().V1().Services() secretsInformer := sharedInformerFactory.Core().V1().Secrets() + testLog = testlogger.New(t) _ = NewImpersonatorConfigController( installedInNamespace, @@ -90,6 +93,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { nil, caSignerName, nil, + testLog, ) credIssuerInformerFilter = observableWithInformerOption.GetFilterForInformer(credIssuerInformer) servicesInformerFilter = observableWithInformerOption.GetFilterForInformer(servicesInformer) @@ -288,6 +292,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { var testHTTPServerInterruptCh chan struct{} var queue *testQueue var validClientCert *tls.Certificate + var testLog *testlogger.Logger var impersonatorFunc = func( port int, @@ -536,6 +541,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { impersonatorFunc, caSignerName, signingCertProvider, + testLog, ) // Set this at the last second to support calling subject.Name(). @@ -1038,6 +1044,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { signingCASecret = newSigningKeySecret(caSignerName, signingCACertPEM, signingCAKeyPEM) validClientCert, err = ca.IssueClientCert("username", nil, time.Hour) r.NoError(err) + testLog = testlogger.New(t) }) it.After(func() { diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index 278b2438..60c759f9 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -269,6 +269,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { impersonator.New, c.NamesConfig.ImpersonationSignerSecret, c.ImpersonationSigningCertProvider, + klogr.New(), ), singletonWorker, ). From 1a4687a40a65bd7f861d24ebc6e2e6d401bac799 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 26 May 2021 12:54:40 -0500 Subject: [PATCH 31/43] Switch impersonatorconfig to all singleton queues. We also no longer need an initial event, since we don't do anything unless the CredentialIssuer exists, so we'll always be triggered at the appropriate time. Signed-off-by: Matt Moyer --- .../impersonatorconfig/impersonator_config.go | 13 +++++-------- .../impersonatorconfig/impersonator_config_test.go | 12 ------------ internal/controllermanager/prepare_controllers.go | 1 - 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 6af92288..2e6a106a 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -93,7 +93,6 @@ func NewImpersonatorConfigController( servicesInformer corev1informers.ServiceInformer, secretsInformer corev1informers.SecretInformer, withInformer pinnipedcontroller.WithInformerOptionFunc, - withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc, generatedLoadBalancerServiceName string, generatedClusterIPServiceName string, tlsSecretName string, @@ -140,20 +139,18 @@ func NewImpersonatorConfigController( ), withInformer( servicesInformer, - pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(generatedLoadBalancerServiceName, namespace), + pinnipedcontroller.SimpleFilterWithSingletonQueue(func(obj metav1.Object) bool { + return obj.GetNamespace() == namespace && obj.GetName() == generatedLoadBalancerServiceName + }), controllerlib.InformerOption{}, ), withInformer( secretsInformer, - pinnipedcontroller.SimpleFilter(func(obj metav1.Object) bool { + pinnipedcontroller.SimpleFilterWithSingletonQueue(func(obj metav1.Object) bool { return obj.GetNamespace() == namespace && secretNames.Has(obj.GetName()) - }, nil), + }), controllerlib.InformerOption{}, ), - // Be sure to run once even if the CredentialIssuer that the informer is watching doesn't exist so we can implement - // the default configuration behavior. - withInitialEvent(controllerlib.Key{Name: credentialIssuerResourceName}), - // TODO fix these controller options to make this a singleton queue ) } diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index c9dbe992..349ee974 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -57,7 +57,6 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { var r *require.Assertions var observableWithInformerOption *testutil.ObservableWithInformerOption - var observableWithInitialEventOption *testutil.ObservableWithInitialEventOption var credIssuerInformerFilter controllerlib.Filter var servicesInformerFilter controllerlib.Filter var secretsInformerFilter controllerlib.Filter @@ -66,7 +65,6 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { it.Before(func() { r = require.New(t) observableWithInformerOption = testutil.NewObservableWithInformerOption() - observableWithInitialEventOption = testutil.NewObservableWithInitialEventOption() pinnipedInformerFactory := pinnipedinformers.NewSharedInformerFactory(nil, 0) sharedInformerFactory := kubeinformers.NewSharedInformerFactory(nil, 0) credIssuerInformer := pinnipedInformerFactory.Config().V1alpha1().CredentialIssuers() @@ -83,7 +81,6 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { servicesInformer, secretsInformer, observableWithInformerOption.WithInformer, - observableWithInitialEventOption.WithInitialEvent, generatedLoadBalancerServiceName, generatedClusterIPServiceName, tlsSecretName, @@ -240,14 +237,6 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { }) }) }) - - when("starting up", func() { - it("asks for an initial event because the CredentialIssuer may not exist yet and it needs to run anyway", func() { - r.Equal(&controllerlib.Key{ - Name: credentialIssuerResourceName, - }, observableWithInitialEventOption.GetInitialEventKey()) - }) - }) }, spec.Parallel(), spec.Report(report.Terminal{})) } @@ -531,7 +520,6 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { kubeInformers.Core().V1().Services(), kubeInformers.Core().V1().Secrets(), controllerlib.WithInformer, - controllerlib.WithInitialEvent, loadBalancerServiceName, clusterIPServiceName, tlsSecretName, diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index 60c759f9..e6b69ec1 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -259,7 +259,6 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { informers.installationNamespaceK8s.Core().V1().Services(), informers.installationNamespaceK8s.Core().V1().Secrets(), controllerlib.WithInformer, - controllerlib.WithInitialEvent, c.NamesConfig.ImpersonationLoadBalancerService, c.NamesConfig.ImpersonationClusterIPService, c.NamesConfig.ImpersonationTLSCertificateSecret, From be8118ec2e420b264b2af63182008b91ab1d501d Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 26 May 2021 12:57:51 -0500 Subject: [PATCH 32/43] Re-enable parallelism on TestImpersonatorConfigControllerSync. Signed-off-by: Matt Moyer --- .../controller/impersonatorconfig/impersonator_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 349ee974..ec26fe1c 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -3548,7 +3548,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) }) - }, spec.Report(report.Terminal{})) // TODO: replace the Parallel() call here + }, spec.Parallel(), spec.Report(report.Terminal{})) } type testQueue struct { From 1932b03c3980b35a9d85873f8cacfe9aef522d92 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 26 May 2021 15:03:04 -0500 Subject: [PATCH 33/43] Refactor createOrUpdateService() method. This updates the code to use a different mechanism for driving desired state: - Read existing object - If it does not exist, create desired object - If it does exist, make a copy and set all the desired fields - Do a deepequal to see if an update is necessary. Signed-off-by: Matt Moyer --- .../impersonatorconfig/impersonator_config.go | 72 ++++++++----------- .../impersonator_config_test.go | 27 +++++++ 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 2e6a106a..431dd6c1 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -11,12 +11,12 @@ import ( "encoding/pem" "fmt" "net" - "reflect" "strings" "time" "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/clock" @@ -232,7 +232,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context, cre } } else { if err = c.ensureImpersonatorIsStopped(true); err != nil { - return nil, err // TODO write unit test that errors during stopping the server are returned by sync + return nil, err } } @@ -343,21 +343,6 @@ func (c *impersonatorConfigController) serviceExists(serviceName string) (bool, return true, nil } -func (c *impersonatorConfigController) serviceNeedsUpdate(config *v1alpha1.ImpersonationProxySpec, serviceName string) (*v1.Service, bool, error) { - service, err := c.servicesInformer.Lister().Services(c.namespace).Get(serviceName) - if err != nil { - return nil, false, err - } - // TODO this will break if anything other than pinniped is adding annotations - if !reflect.DeepEqual(service.Annotations, config.Service.Annotations) { - return service, true, nil - } - if service.Spec.LoadBalancerIP != config.Service.LoadBalancerIP { - return service, true, nil - } - return nil, false, nil -} - func (c *impersonatorConfigController) tlsSecretExists() (bool, *v1.Secret, error) { secret, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) @@ -465,7 +450,7 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.C Annotations: config.Service.Annotations, }, } - return c.createOrUpdateService(ctx, config, &loadBalancer) + return c.createOrUpdateService(ctx, &loadBalancer) } func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.Context) error { @@ -504,7 +489,7 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx conte Annotations: config.Service.Annotations, }, } - return c.createOrUpdateService(ctx, config, &clusterIP) + return c.createOrUpdateService(ctx, &clusterIP) } func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx context.Context) error { @@ -522,36 +507,35 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx conte return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{}) } -func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context, config *v1alpha1.ImpersonationProxySpec, service *v1.Service) error { - running, err := c.serviceExists(service.Name) +func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context, service *v1.Service) error { + log := c.infoLog.WithValues("serviceType", service.Spec.Type, "service", klog.KObj(service)) + existing, err := c.servicesInformer.Lister().Services(c.namespace).Get(service.Name) + if k8serrors.IsNotFound(err) { + log.Info("creating service for impersonation proxy") + _, err := c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, service, metav1.CreateOptions{}) + return err + } if err != nil { return err } - if running { - existingService, needsUpdate, err := c.serviceNeedsUpdate(config, service.Name) - if err != nil { - return err - } - if needsUpdate { - c.infoLog.Info("updating service for impersonation proxy", - "serviceType", service.Spec.Type, - "service", klog.KObj(service), - ) - // update only the annotation and loadBalancerIP fields on the service - var newService = &v1.Service{} - existingService.DeepCopyInto(newService) - newService.Annotations = service.Annotations - newService.Spec.LoadBalancerIP = service.Spec.LoadBalancerIP - _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, newService, metav1.UpdateOptions{}) - return err - } + + // Update only the specific fields that are meaningfully part of our desired state. + updated := existing.DeepCopy() + updated.ObjectMeta.Labels = service.ObjectMeta.Labels + updated.ObjectMeta.Annotations = service.ObjectMeta.Annotations + updated.Spec.LoadBalancerIP = service.Spec.LoadBalancerIP + updated.Spec.Type = service.Spec.Type + updated.Spec.Selector = service.Spec.Selector + updated.Spec.Ports = service.Spec.Ports + + // If our updates didn't change anything, we're done. + if equality.Semantic.DeepEqual(existing, updated) { return nil } - c.infoLog.Info("creating service for impersonation proxy", - "serviceType", service.Spec.Type, - "service", klog.KObj(service), - ) - _, err = c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, service, metav1.CreateOptions{}) + + // Otherwise apply the updates. + c.infoLog.Info("updating service for impersonation proxy") + _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, updated, metav1.UpdateOptions{}) return err } diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index ec26fe1c..2a763603 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/clock" + "k8s.io/apimachinery/pkg/util/intstr" kubeinformers "k8s.io/client-go/informers" kubernetesfake "k8s.io/client-go/kubernetes/fake" coretesting "k8s.io/client-go/testing" @@ -618,9 +619,18 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: resourceName, Namespace: installedInNamespace, + Labels: labels, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, + Ports: []corev1.ServicePort{ + { + TargetPort: intstr.FromInt(impersonationProxyPort), + Port: defaultHTTPSPort, + Protocol: corev1.ProtocolTCP, + }, + }, + Selector: map[string]string{appLabelKey: labels[appLabelKey]}, }, Status: status, } @@ -631,6 +641,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: resourceName, Namespace: installedInNamespace, + Labels: labels, }, Spec: spec, Status: status, @@ -737,6 +748,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { clusterIPService := newClusterIPService(resourceName, corev1.ServiceStatus{}, corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, ClusterIP: clusterIP, + Ports: []corev1.ServicePort{ + { + TargetPort: intstr.FromInt(impersonationProxyPort), + Port: defaultHTTPSPort, + Protocol: corev1.ProtocolTCP, + }, + }, + Selector: map[string]string{appLabelKey: labels[appLabelKey]}, }) r.NoError(client.Tracker().Add(clusterIPService)) } @@ -746,6 +765,14 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { Type: corev1.ServiceTypeClusterIP, ClusterIP: clusterIP0, ClusterIPs: []string{clusterIP0, clusterIP1}, + Ports: []corev1.ServicePort{ + { + TargetPort: intstr.FromInt(impersonationProxyPort), + Port: defaultHTTPSPort, + Protocol: corev1.ProtocolTCP, + }, + }, + Selector: map[string]string{appLabelKey: labels[appLabelKey]}, }) r.NoError(client.Tracker().Add(clusterIPService)) } From b57878ebc5824aa6e1c3bec261ad46bd9e89ae45 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 26 May 2021 15:08:29 -0500 Subject: [PATCH 34/43] Remove TODO from impersonator.go. We're now tracking this in an issue: https://github.com/vmware-tanzu/pinniped/issues/642 Signed-off-by: Matt Moyer --- internal/concierge/impersonator/impersonator.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/concierge/impersonator/impersonator.go b/internal/concierge/impersonator/impersonator.go index 5778b795..2eeaf25d 100644 --- a/internal/concierge/impersonator/impersonator.go +++ b/internal/concierge/impersonator/impersonator.go @@ -195,7 +195,6 @@ func newInternal( //nolint:funlen // yeah, it's kind of long. } // wire up a fake audit backend at the metadata level so we can preserve the original user during nested impersonation - // TODO: wire up the real std out logging audit backend based on plog log level serverConfig.AuditPolicyChecker = policy.FakeChecker(auditinternal.LevelMetadata, nil) serverConfig.AuditBackend = &auditfake.Backend{} From d780bf64bc8bd159066ffd34a3401aa778c91e86 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 26 May 2021 15:24:59 -0500 Subject: [PATCH 35/43] Remove references to impersonationConfigMap. Signed-off-by: Margo Crawford --- deploy/concierge/deployment.yaml | 1 - internal/config/concierge/config.go | 3 -- internal/config/concierge/config_test.go | 38 ++---------------------- internal/config/concierge/types.go | 9 ++---- 4 files changed, 6 insertions(+), 45 deletions(-) diff --git a/deploy/concierge/deployment.yaml b/deploy/concierge/deployment.yaml index edef2fd8..c00ce5e4 100644 --- a/deploy/concierge/deployment.yaml +++ b/deploy/concierge/deployment.yaml @@ -49,7 +49,6 @@ data: servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @) credentialIssuer: (@= defaultResourceNameWithSuffix("config") @) apiService: (@= defaultResourceNameWithSuffix("api") @) - impersonationConfigMap: (@= defaultResourceNameWithSuffix("impersonation-proxy-config") @) impersonationLoadBalancerService: (@= defaultResourceNameWithSuffix("impersonation-proxy-load-balancer") @) impersonationClusterIPService: (@= defaultResourceNameWithSuffix("impersonation-proxy-cluster-ip") @) impersonationTLSCertificateSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-tls-serving-certificate") @) diff --git a/internal/config/concierge/config.go b/internal/config/concierge/config.go index bb7fbee8..cbe9d7f9 100644 --- a/internal/config/concierge/config.go +++ b/internal/config/concierge/config.go @@ -108,9 +108,6 @@ func validateNames(names *NamesConfigSpec) error { if names.APIService == "" { missingNames = append(missingNames, "apiService") } - if names.ImpersonationConfigMap == "" { - missingNames = append(missingNames, "impersonationConfigMap") - } if names.ImpersonationLoadBalancerService == "" { missingNames = append(missingNames, "impersonationLoadBalancerService") } diff --git a/internal/config/concierge/config_test.go b/internal/config/concierge/config_test.go index 5d4097ea..c29fb5f6 100644 --- a/internal/config/concierge/config_test.go +++ b/internal/config/concierge/config_test.go @@ -38,7 +38,6 @@ func TestFromPath(t *testing.T) { credentialIssuer: pinniped-config apiService: pinniped-api kubeCertAgentPrefix: kube-cert-agent-prefix - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value @@ -46,6 +45,7 @@ func TestFromPath(t *testing.T) { impersonationSignerSecret: impersonationSignerSecret-value impersonationSignerSecret: impersonationSignerSecret-value agentServiceAccount: agentServiceAccount-value + extraName: extraName-value labels: myLabelKey1: myLabelValue1 myLabelKey2: myLabelValue2 @@ -70,7 +70,6 @@ func TestFromPath(t *testing.T) { ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate", CredentialIssuer: "pinniped-config", APIService: "pinniped-api", - ImpersonationConfigMap: "impersonationConfigMap-value", ImpersonationLoadBalancerService: "impersonationLoadBalancerService-value", ImpersonationClusterIPService: "impersonationClusterIPService-value", ImpersonationTLSCertificateSecret: "impersonationTLSCertificateSecret-value", @@ -98,7 +97,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value @@ -121,7 +119,6 @@ func TestFromPath(t *testing.T) { ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate", CredentialIssuer: "pinniped-config", APIService: "pinniped-api", - ImpersonationConfigMap: "impersonationConfigMap-value", ImpersonationLoadBalancerService: "impersonationLoadBalancerService-value", ImpersonationClusterIPService: "impersonationClusterIPService-value", ImpersonationTLSCertificateSecret: "impersonationTLSCertificateSecret-value", @@ -140,7 +137,7 @@ func TestFromPath(t *testing.T) { name: "Empty", yaml: here.Doc(``), wantError: "validate names: missing required names: servingCertificateSecret, credentialIssuer, " + - "apiService, impersonationConfigMap, impersonationLoadBalancerService, " + + "apiService, impersonationLoadBalancerService, " + "impersonationClusterIPService, impersonationTLSCertificateSecret, impersonationCACertificateSecret, " + "impersonationSignerSecret, agentServiceAccount", }, @@ -151,7 +148,6 @@ func TestFromPath(t *testing.T) { names: servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value @@ -168,7 +164,6 @@ func TestFromPath(t *testing.T) { names: servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value @@ -185,7 +180,6 @@ func TestFromPath(t *testing.T) { names: credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value @@ -195,23 +189,6 @@ func TestFromPath(t *testing.T) { `), wantError: "validate names: missing required names: servingCertificateSecret", }, - { - name: "Missing impersonationConfigMap name", - yaml: here.Doc(` - --- - names: - servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate - credentialIssuer: pinniped-config - apiService: pinniped-api - impersonationLoadBalancerService: impersonationLoadBalancerService-value - impersonationClusterIPService: impersonationClusterIPService-value - impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value - impersonationCACertificateSecret: impersonationCACertificateSecret-value - impersonationSignerSecret: impersonationSignerSecret-value - agentServiceAccount: agentServiceAccount-value - `), - wantError: "validate names: missing required names: impersonationConfigMap", - }, { name: "Missing impersonationLoadBalancerService name", yaml: here.Doc(` @@ -220,7 +197,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value @@ -237,7 +213,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value @@ -254,7 +229,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationCACertificateSecret: impersonationCACertificateSecret-value @@ -271,7 +245,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value @@ -288,7 +261,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationClusterIPService: impersonationClusterIPService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value @@ -310,7 +282,7 @@ func TestFromPath(t *testing.T) { impersonationSignerSecret: impersonationSignerSecret-value agentServiceAccount: agentServiceAccount-value `), - wantError: "validate names: missing required names: impersonationConfigMap, " + + wantError: "validate names: missing required names: " + "impersonationTLSCertificateSecret, impersonationCACertificateSecret", }, { @@ -325,7 +297,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value @@ -345,7 +316,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value @@ -365,7 +335,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value @@ -386,7 +355,6 @@ func TestFromPath(t *testing.T) { servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate credentialIssuer: pinniped-config apiService: pinniped-api - impersonationConfigMap: impersonationConfigMap-value impersonationLoadBalancerService: impersonationLoadBalancerService-value impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value impersonationCACertificateSecret: impersonationCACertificateSecret-value diff --git a/internal/config/concierge/types.go b/internal/config/concierge/types.go index ea174e65..6aa6733a 100644 --- a/internal/config/concierge/types.go +++ b/internal/config/concierge/types.go @@ -33,12 +33,9 @@ type APIConfigSpec struct { // NamesConfigSpec configures the names of some Kubernetes resources for the Concierge. type NamesConfigSpec struct { - ServingCertificateSecret string `json:"servingCertificateSecret"` - CredentialIssuer string `json:"credentialIssuer"` - APIService string `json:"apiService"` - - // TODO: remove this key entirely - ImpersonationConfigMap string `json:"impersonationConfigMap"` + ServingCertificateSecret string `json:"servingCertificateSecret"` + CredentialIssuer string `json:"credentialIssuer"` + APIService string `json:"apiService"` ImpersonationLoadBalancerService string `json:"impersonationLoadBalancerService"` ImpersonationClusterIPService string `json:"impersonationClusterIPService"` ImpersonationTLSCertificateSecret string `json:"impersonationTLSCertificateSecret"` From 0a47aa48433df1540a296074176ea8080a9cbad7 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Wed, 26 May 2021 16:47:02 -0500 Subject: [PATCH 36/43] Adjust log levels in impersonatorconfig controller. Signed-off-by: Margo Crawford --- .../controller/impersonatorconfig/impersonator_config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 431dd6c1..789c4caf 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -127,8 +127,8 @@ func NewImpersonatorConfigController( impersonationSigningCertProvider: impersonationSigningCertProvider, impersonatorFunc: impersonatorFunc, tlsServingCertDynamicCertProvider: dynamiccert.NewServingCert("impersonation-proxy-serving-cert"), - infoLog: log, - debugLog: log.V(10), + infoLog: log.V(2), + debugLog: log.V(4), }, }, withInformer(credentialIssuerInformer, @@ -299,7 +299,7 @@ func (c *impersonatorConfigController) loadImpersonationProxyConfiguration(credI if err := validateCredentialIssuerSpec(spec); err != nil { return nil, fmt.Errorf("could not load CredentialIssuer spec.impersonationProxy: %w", err) } - c.infoLog.Info("read impersonation proxy config", "credentialIssuer", c.credentialIssuerResourceName) + c.debugLog.Info("read impersonation proxy config", "credentialIssuer", c.credentialIssuerResourceName) return spec, nil } @@ -926,7 +926,7 @@ func (c *impersonatorConfigController) loadSignerCA(status v1alpha1.StrategyStat } func (c *impersonatorConfigController) clearSignerCA() { - c.infoLog.Info("clearing credential signing certificate for impersonation proxy") + c.debugLog.Info("clearing credential signing certificate for impersonation proxy") c.impersonationSigningCertProvider.UnsetCertKeyContent() } From d2d0dae4edce4b5b420a46a1463f8699f161ee39 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Wed, 26 May 2021 15:52:31 -0700 Subject: [PATCH 37/43] Wait for credentialissuer to be updated and always use proxy on clusterip test --- .../concierge_impersonation_proxy_test.go | 70 ++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index dff36ac0..8fff71a7 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -121,7 +121,10 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl mostRecentTokenCredentialRequestResponse *loginv1alpha1.TokenCredentialRequest mostRecentTokenCredentialRequestResponseLock sync.Mutex ) - refreshCredential := func(t *testing.T, impersonationProxyURL string, impersonationProxyCACertPEM []byte) *loginv1alpha1.ClusterCredential { + + refreshCredentialHelper := func(t *testing.T, client pinnipedconciergeclientset.Interface) *loginv1alpha1.ClusterCredential { + t.Helper() + mostRecentTokenCredentialRequestResponseLock.Lock() defer mostRecentTokenCredentialRequestResponseLock.Unlock() if mostRecentTokenCredentialRequestResponse == nil || credentialAlmostExpired(t, mostRecentTokenCredentialRequestResponse) { @@ -133,11 +136,6 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // However, we issue short-lived certs, so this cert will only be valid for a few minutes. // Cache it until it is almost expired and then refresh it whenever it is close to expired. // - // Also, use an anonymous client which goes through the impersonation proxy to make the request because that's - // what would normally happen when a user is using a kubeconfig where the server is the impersonation proxy, - // so it more closely simulates the normal use case, and also because we want this to work on AKS clusters - // which do not allow anonymous requests. - client := newAnonymousImpersonationProxyClient(t, impersonationProxyURL, impersonationProxyCACertPEM, nil).PinnipedConcierge require.Eventually(t, func() bool { mostRecentTokenCredentialRequestResponse, err = createTokenCredentialRequest(credentialRequestSpecWithWorkingCredentials, client) if err != nil { @@ -156,9 +154,19 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // tokens, we should revisit this test's rest config below. require.Empty(t, mostRecentTokenCredentialRequestResponse.Status.Credential.Token) } + return mostRecentTokenCredentialRequestResponse.Status.Credential } + refreshCredential := func(t *testing.T, impersonationProxyURL string, impersonationProxyCACertPEM []byte) *loginv1alpha1.ClusterCredential { + // Use an anonymous client which goes through the impersonation proxy to make the request because that's + // what would normally happen when a user is using a kubeconfig where the server is the impersonation proxy, + // so it more closely simulates the normal use case, and also because we want this to work on AKS clusters + // which do not allow anonymous requests. + client := newAnonymousImpersonationProxyClient(t, impersonationProxyURL, impersonationProxyCACertPEM, nil).PinnipedConcierge + return refreshCredentialHelper(t, client) + } + oldCredentialIssuer, err := adminConciergeClient.ConfigV1alpha1().CredentialIssuers().Get(ctx, credentialIssuerName(env), metav1.GetOptions{}) require.NoError(t, err) // At the end of the test, clean up the CredentialIssuer @@ -1214,18 +1222,18 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl }, }, }) - // Wait until the clusterip exists - library.RequireEventuallyWithoutError(t, func() (bool, error) { - return hasImpersonationProxyClusterIPService(ctx, env, adminClient) - }, 30*time.Second, 500*time.Millisecond) + // wait until the credential issuer is updated with the new url + require.Eventually(t, func() bool { + newImpersonationProxyURL, _ := performImpersonatorDiscovery(ctx, t, env, adminConciergeClient) + return newImpersonationProxyURL == "https://"+clusterIPServiceURL + }, 30*time.Second, 500*time.Millisecond) newImpersonationProxyURL, newImpersonationProxyCACertPEM := performImpersonatorDiscovery(ctx, t, env, adminConciergeClient) - refreshedCredentials := refreshCredential(t, impersonationProxyURL, impersonationProxyCACertPEM).DeepCopy() - kubeconfig := impersonationProxyRestConfig(refreshedCredentials, newImpersonationProxyURL, newImpersonationProxyCACertPEM, nil) - kubeconfig.Proxy = kubeconfigProxyFunc(t, env.Proxy) + anonymousClient := newAnonymousImpersonationProxyClientWithProxy(t, newImpersonationProxyURL, newImpersonationProxyCACertPEM, nil).PinnipedConcierge + refreshedCredentials := refreshCredentialHelper(t, anonymousClient) - client := library.NewKubeclient(t, kubeconfig).Kubernetes + client := newImpersonationProxyClientWithCredentialsAndProxy(t, refreshedCredentials, newImpersonationProxyURL, newImpersonationProxyCACertPEM, nil).Kubernetes // everything should work properly through the cluster ip service t.Run( @@ -1536,17 +1544,6 @@ func loadBalancerHasAnnotations(ctx context.Context, env *library.TestEnv, clien return service.Spec.Type == corev1.ServiceTypeLoadBalancer && hasExactAnnotations, nil } -func hasImpersonationProxyClusterIPService(ctx context.Context, env *library.TestEnv, client kubernetes.Interface) (bool, error) { - service, err := client.CoreV1().Services(env.ConciergeNamespace).Get(ctx, impersonationProxyClusterIPName(env), metav1.GetOptions{}) - if k8serrors.IsNotFound(err) { - return false, nil - } - if err != nil { - return false, err - } - return service.Spec.Type == corev1.ServiceTypeClusterIP, nil -} - func impersonationProxyTLSSecretName(env *library.TestEnv) string { return env.ConciergeAppName + "-impersonation-proxy-tls-serving-certificate" } @@ -1714,6 +1711,29 @@ func newAnonymousImpersonationProxyClient(t *testing.T, impersonationProxyURL st return newImpersonationProxyClientWithCredentials(t, emptyCredentials, impersonationProxyURL, impersonationProxyCACertPEM, nestedImpersonationConfig) } +func newImpersonationProxyClientWithCredentialsAndProxy(t *testing.T, credentials *loginv1alpha1.ClusterCredential, impersonationProxyURL string, impersonationProxyCACertPEM []byte, nestedImpersonationConfig *rest.ImpersonationConfig) *kubeclient.Client { + t.Helper() + + env := library.IntegrationEnv(t) + + kubeconfig := impersonationProxyRestConfig(credentials, impersonationProxyURL, impersonationProxyCACertPEM, nestedImpersonationConfig) + kubeconfig.Proxy = kubeconfigProxyFunc(t, env.Proxy) + return library.NewKubeclient(t, kubeconfig) +} + +// this uses a proxy in all cases, the other will only use it if you don't have load balancer capabilities. +func newAnonymousImpersonationProxyClientWithProxy(t *testing.T, impersonationProxyURL string, impersonationProxyCACertPEM []byte, nestedImpersonationConfig *rest.ImpersonationConfig) *kubeclient.Client { + t.Helper() + env := library.IntegrationEnv(t) + + emptyCredentials := &loginv1alpha1.ClusterCredential{} + kubeconfig := impersonationProxyRestConfig(emptyCredentials, impersonationProxyURL, impersonationProxyCACertPEM, nestedImpersonationConfig) + + kubeconfig.Proxy = kubeconfigProxyFunc(t, env.Proxy) + + return library.NewKubeclient(t, kubeconfig) +} + func impersonationProxyViaSquidKubeClientWithoutCredential(t *testing.T, proxyServiceEndpoint string) kubernetes.Interface { t.Helper() From 049abfb94c9414e2d2579ab2cce968048c7d6c18 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 27 May 2021 09:22:47 -0500 Subject: [PATCH 38/43] Remove a "fail fast" check from TestImpersonationProxy. This check is no longer valid, because there can be ephemeral, recoverable errors that show as ErrorDuringSetup. Signed-off-by: Matt Moyer --- test/integration/concierge_impersonation_proxy_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index 8fff71a7..192db0a4 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -1413,10 +1413,6 @@ func performImpersonatorDiscovery(ctx context.Context, t *testing.T, env *librar } else if strategy.Type == conciergev1alpha.ImpersonationProxyStrategyType { t.Logf("Waiting for successful impersonation proxy strategy on %s: found status %s with reason %s and message: %s", credentialIssuerName(env), strategy.Status, strategy.Reason, strategy.Message) - if strategy.Reason == conciergev1alpha.ErrorDuringSetupStrategyReason { - // The server encountered an unexpected error while starting the impersonator, so fail the test fast. - return false, fmt.Errorf("found impersonation strategy in %s state with message: %s", strategy.Reason, strategy.Message) - } } } t.Log("Did not find any impersonation proxy strategy on CredentialIssuer") From 349d3dad83e6018fcbccebdcd9c71dfc3e540db6 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 27 May 2021 11:13:10 -0500 Subject: [PATCH 39/43] Make temporary errors return Pending in impersonatorconfig. Signed-off-by: Matt Moyer --- .../impersonatorconfig/impersonator_config.go | 14 ++++- .../impersonator_config_test.go | 62 +++++++++++-------- .../concierge_impersonation_proxy_test.go | 4 ++ 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 789c4caf..6dfc243c 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -168,7 +168,7 @@ func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error strategy = &v1alpha1.CredentialIssuerStrategy{ Type: v1alpha1.ImpersonationProxyStrategyType, Status: v1alpha1.ErrorStrategyStatus, - Reason: v1alpha1.ErrorDuringSetupStrategyReason, + Reason: strategyReasonForError(err), Message: err.Error(), LastUpdateTime: metav1.NewTime(c.clock.Now()), } @@ -189,6 +189,18 @@ func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error return err } +// strategyReasonForError returns the proper v1alpha1.StrategyReason for a sync error. Some errors are occasionally +// expected because there are multiple pods running, in these cases we should report a Pending reason and we'll +// recover on a following sync. +func strategyReasonForError(err error) v1alpha1.StrategyReason { + switch { + case k8serrors.IsConflict(err), k8serrors.IsAlreadyExists(err): + return v1alpha1.PendingStrategyReason + default: + return v1alpha1.ErrorDuringSetupStrategyReason + } +} + type certNameInfo struct { // ready will be true when the certificate name information is known. // ready will be false when it is pending because we are waiting for a load balancer to get assigned an ip/hostname. diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 2a763603..91db0a76 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -856,17 +857,21 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { return s } - var newPendingStrategy = func() v1alpha1.CredentialIssuerStrategy { + var newPendingStrategy = func(msg string) v1alpha1.CredentialIssuerStrategy { return v1alpha1.CredentialIssuerStrategy{ Type: v1alpha1.ImpersonationProxyStrategyType, Status: v1alpha1.ErrorStrategyStatus, Reason: v1alpha1.PendingStrategyReason, - Message: "waiting for load balancer Service to be assigned IP or hostname", + Message: msg, LastUpdateTime: metav1.NewTime(frozenNow), Frontend: nil, } } + var newPendingStrategyWaitingForLB = func() v1alpha1.CredentialIssuerStrategy { + return newPendingStrategy("waiting for load balancer Service to be assigned IP or hostname") + } + var newErrorStrategy = func(msg string) v1alpha1.CredentialIssuerStrategy { return v1alpha1.CredentialIssuerStrategy{ Type: v1alpha1.ImpersonationProxyStrategyType, @@ -1204,7 +1209,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireNodesListed(kubeAPIClient.Actions()[0]) requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -1223,7 +1228,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Len(kubeAPIClient.Actions(), 2) requireNodesListed(kubeAPIClient.Actions()[0]) requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -1242,7 +1247,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.Len(kubeAPIClient.Actions(), 2) requireNodesListed(kubeAPIClient.Actions()[0]) requireCASecretWasCreated(kubeAPIClient.Actions()[1]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -1492,7 +1497,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) @@ -1527,7 +1532,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireNodesListed(kubeAPIClient.Actions()[0]) requireCASecretWasCreated(kubeAPIClient.Actions()[1]) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) @@ -1665,7 +1670,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { require.Equal(t, lbService.Annotations, annotations) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -1760,7 +1765,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireCASecretWasCreated(kubeAPIClient.Actions()[2]) // Check that the server is running without certs. requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -2140,7 +2145,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireNodesListed(kubeAPIClient.Actions()[0]) requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Simulate the informer cache's background update from its watch. @@ -2175,7 +2180,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireTLSServerIsRunningWithoutCerts() r.Len(kubeAPIClient.Actions(), 5) requireLoadBalancerWasCreated(kubeAPIClient.Actions()[4]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -2206,7 +2211,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireNodesListed(kubeAPIClient.Actions()[0]) requireClusterIPWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Simulate the informer cache's background update from its watch. @@ -2244,7 +2249,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireTLSServerIsRunningWithoutCerts() r.Len(kubeAPIClient.Actions(), 5) requireClusterIPWasCreated(kubeAPIClient.Actions()[4]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -2297,7 +2302,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireLoadBalancerWasCreated(kubeAPIClient.Actions()[3]) requireTLSSecretWasDeleted(kubeAPIClient.Actions()[4]) // the Secret was deleted because it contained a cert with the wrong IP requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Simulate the informer cache's background update from its watch. @@ -2309,7 +2314,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(runControllerSync()) r.Len(kubeAPIClient.Actions(), 5) // no new actions while it is waiting for the load balancer's ingress requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Update the ingress of the LB in the informer's client and run Sync again. @@ -2554,7 +2559,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Simulate the informer cache's background update from its watch. @@ -2564,7 +2569,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { r.NoError(runControllerSync()) r.Equal(1, impersonatorFuncWasCalled) // wasn't started a second time requireTLSServerIsRunningWithoutCerts() // still running - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() r.Len(kubeAPIClient.Actions(), 3) // no new API calls }) @@ -2577,7 +2582,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Simulate the informer cache's background update from its watch. @@ -2614,7 +2619,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Simulate the informer cache's background update from its watch. @@ -2682,7 +2687,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) credentialIssuer := getCredentialIssuer() - r.Equal([]v1alpha1.CredentialIssuerStrategy{preExistingStrategy, newPendingStrategy()}, credentialIssuer.Status.Strategies) + r.Equal([]v1alpha1.CredentialIssuerStrategy{preExistingStrategy, newPendingStrategyWaitingForLB()}, credentialIssuer.Status.Strategies) }) }) @@ -2735,7 +2740,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireNodesListed(kubeAPIClient.Actions()[0]) requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() // Simulate the informer cache's background update from its watch. @@ -2763,7 +2768,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Now everything should be working correctly. r.NoError(runControllerSync()) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -2791,7 +2796,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { requireNodesListed(kubeAPIClient.Actions()[0]) requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) requireCASecretWasCreated(kubeAPIClient.Actions()[2]) - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() requireTLSServerIsRunningWithoutCerts() @@ -2823,7 +2828,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { // Now everything should be working correctly. r.NoError(runControllerSync()) requireTLSServerIsRunningWithoutCerts() - requireCredentialIssuer(newPendingStrategy()) + requireCredentialIssuer(newPendingStrategyWaitingForLB()) requireSigningCertProviderIsEmpty() }) }) @@ -2947,7 +2952,10 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it.Before(func() { addNodeWithRoleToTracker("worker", kubeAPIClient) kubeAPIClient.PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - return true, nil, fmt.Errorf("error on create") + return true, nil, k8serrors.NewAlreadyExists( + action.GetResource().GroupResource(), + action.(coretesting.CreateAction).GetObject().(*corev1.Service).Name, + ) }) addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{ ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName}, @@ -2961,8 +2969,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { it("returns an error", func() { startInformersAndController() - r.EqualError(runControllerSync(), "error on create") - requireCredentialIssuer(newErrorStrategy("error on create")) + r.EqualError(runControllerSync(), `services "some-service-resource-name" already exists`) + requireCredentialIssuer(newPendingStrategy(`services "some-service-resource-name" already exists`)) requireSigningCertProviderIsEmpty() requireTLSServerIsRunningWithoutCerts() }) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index 192db0a4..8fff71a7 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -1413,6 +1413,10 @@ func performImpersonatorDiscovery(ctx context.Context, t *testing.T, env *librar } else if strategy.Type == conciergev1alpha.ImpersonationProxyStrategyType { t.Logf("Waiting for successful impersonation proxy strategy on %s: found status %s with reason %s and message: %s", credentialIssuerName(env), strategy.Status, strategy.Reason, strategy.Message) + if strategy.Reason == conciergev1alpha.ErrorDuringSetupStrategyReason { + // The server encountered an unexpected error while starting the impersonator, so fail the test fast. + return false, fmt.Errorf("found impersonation strategy in %s state with message: %s", strategy.Reason, strategy.Message) + } } } t.Log("Did not find any impersonation proxy strategy on CredentialIssuer") From 67d5c9171375a483f0220a39b3c158ed493d1cfe Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 27 May 2021 13:03:07 -0500 Subject: [PATCH 40/43] Wait for successful TCR in TestImpersonationProxy. This test setup should tolerate when the TokenCredentialRequest API isn't quite ready to authenticate the user or issue a cert. Signed-off-by: Matt Moyer --- test/integration/concierge_impersonation_proxy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index 8fff71a7..7130e17e 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -142,7 +142,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl t.Logf("failed to make TokenCredentialRequest: %s", library.Sdump(err)) return false } - return true + return mostRecentTokenCredentialRequestResponse.Status.Credential != nil }, 5*time.Minute, 5*time.Second) require.Nil(t, mostRecentTokenCredentialRequestResponse.Status.Message, From f330b5207660fb24c02c5cdbfade75292b43c761 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Thu, 27 May 2021 13:36:18 -0700 Subject: [PATCH 41/43] Update values.yaml to include CredentialIssuer ImpersonationProxy spec. --- deploy/concierge/deployment.yaml | 13 ++++++--- deploy/concierge/values.yaml | 30 ++++++++++++++++++++ site/content/docs/howto/install-concierge.md | 2 ++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/deploy/concierge/deployment.yaml b/deploy/concierge/deployment.yaml index c00ce5e4..31c7fe68 100644 --- a/deploy/concierge/deployment.yaml +++ b/deploy/concierge/deployment.yaml @@ -255,8 +255,13 @@ metadata: labels: #@ labels() spec: impersonationProxy: - mode: auto + mode: #@ data.values.impersonation_proxy_spec.mode + #@ if data.values.impersonation_proxy_spec.external_endpoint: + externalEndpoint: #@ data.values.impersonation_proxy_spec.external_endpoint + #@ end service: - mode: LoadBalancer - annotations: - service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000" + mode: #@ data.values.impersonation_proxy_spec.service.mode + #@ if data.values.impersonation_proxy_spec.service.load_balancer_ip: + loadBalancerIP: #@ data.values.impersonation_proxy_spec.service.load_balancer_ip + #@ end + annotations: #@ data.values.impersonation_proxy_spec.service.annotations diff --git a/deploy/concierge/values.yaml b/deploy/concierge/values.yaml index 05d550db..0445afb2 100644 --- a/deploy/concierge/values.yaml +++ b/deploy/concierge/values.yaml @@ -63,3 +63,33 @@ run_as_group: 1001 #! run_as_group specifies the group ID that will own the proc #! authentication.concierge.pinniped.dev, etc. As an example, if this is set to tuna.io, then #! Pinniped API groups will look like foo.tuna.io. authentication.concierge.tuna.io, etc. api_group_suffix: pinniped.dev + +#! Customize CredentialIssuer.spec.impersonationProxy to change how the concierge +#! handles impersonation. +impersonation_proxy_spec: + #! options are "auto", "disabled" or "enabled". + #! If auto, the impersonation proxy will run only if the cluster signing key is not available + #! and the other strategy does not work. + #! If disabled, the impersonation proxy will never run, which could mean that the concierge + #! doesn't work at all. + #! If enabled, the impersonation proxy will always run regardless of other strategies available. + mode: auto + #! The endpoint which the client should use to connect to the impersonation proxy. + #! If left unset, the client will default to connecting based on the ClusterIP or LoadBalancer + #! endpoint. + external_endpoint: + service: + #! Options are "LoadBalancer", "ClusterIP" and "None". + #! LoadBalancer automatically provisions a Service of type LoadBalancer pointing at + #! the impersonation proxy. Some cloud providers will allocate + #! a public IP address by default even on private clusters. + #! ClusterIP automatically provisions a Service of type ClusterIP pointing at the + #! impersonation proxy. + #! None does not provision either and assumes that you have set the external_endpoint + #! and set up your own ingress to connect to the impersonation proxy. + mode: LoadBalancer + #! The annotations that should be set on the ClusterIP or LoadBalancer Service. + annotations: + {service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000"} + #! When mode LoadBalancer is set, this will set the LoadBalancer Service's Spec.LoadBalancerIP. + load_balancer_ip: diff --git a/site/content/docs/howto/install-concierge.md b/site/content/docs/howto/install-concierge.md index 7eed25d7..9fd06065 100644 --- a/site/content/docs/howto/install-concierge.md +++ b/site/content/docs/howto/install-concierge.md @@ -17,6 +17,8 @@ You should have a [supported Kubernetes cluster]({{< ref "../reference/supported 1. Install the latest version of the Concierge into the `pinniped-concierge` namespace with default options: - `kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge.yaml` + +Warning: the default configuration may create a public LoadBalancer Service on your cluster. ## With specific version and default options From ab750f48aa002504a18602e29f18af561b52cfe0 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 27 May 2021 17:09:12 -0500 Subject: [PATCH 42/43] When merging CredentialIssuer updates, don't overwrite LastUpdated. If the only thing that has changed about a strategy is the LastUpdated timestamp, then we should not update the object. Signed-off-by: Margo Crawford --- .../controller/issuerconfig/issuerconfig.go | 12 ++++++- .../issuerconfig/issuerconfig_test.go | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/internal/controller/issuerconfig/issuerconfig.go b/internal/controller/issuerconfig/issuerconfig.go index b2440203..faa14695 100644 --- a/internal/controller/issuerconfig/issuerconfig.go +++ b/internal/controller/issuerconfig/issuerconfig.go @@ -42,7 +42,9 @@ func mergeStrategy(configToUpdate *v1alpha1.CredentialIssuerStatus, strategy v1a } } if existing != nil { - strategy.DeepCopyInto(existing) + if !equalExceptLastUpdated(existing, &strategy) { + strategy.DeepCopyInto(existing) + } } else { configToUpdate.Strategies = append(configToUpdate.Strategies, strategy) } @@ -75,3 +77,11 @@ func (s sortableStrategies) Less(i, j int) bool { return s[i].Type < s[j].Type } func (s sortableStrategies) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func equalExceptLastUpdated(s1, s2 *v1alpha1.CredentialIssuerStrategy) bool { + s1 = s1.DeepCopy() + s2 = s2.DeepCopy() + s1.LastUpdateTime = metav1.Time{} + s2.LastUpdateTime = metav1.Time{} + return apiequality.Semantic.DeepEqual(s1, s2) +} diff --git a/internal/controller/issuerconfig/issuerconfig_test.go b/internal/controller/issuerconfig/issuerconfig_test.go index 16302499..1ef1600d 100644 --- a/internal/controller/issuerconfig/issuerconfig_test.go +++ b/internal/controller/issuerconfig/issuerconfig_test.go @@ -125,6 +125,38 @@ func TestMergeStrategy(t *testing.T) { }, }, }, + { + name: "existing entry matches except for LastUpdated time", + configToUpdate: v1alpha1.CredentialIssuerStatus{ + Strategies: []v1alpha1.CredentialIssuerStrategy{ + { + Type: "Type1", + Status: v1alpha1.ErrorStrategyStatus, + Reason: "some starting reason", + Message: "some starting message", + LastUpdateTime: t1, + }, + }, + }, + strategy: v1alpha1.CredentialIssuerStrategy{ + Type: "Type1", + Status: v1alpha1.ErrorStrategyStatus, + Reason: "some starting reason", + Message: "some starting message", + LastUpdateTime: t2, + }, + expected: v1alpha1.CredentialIssuerStatus{ + Strategies: []v1alpha1.CredentialIssuerStrategy{ + { + Type: "Type1", + Status: v1alpha1.ErrorStrategyStatus, + Reason: "some starting reason", + Message: "some starting message", + LastUpdateTime: t1, + }, + }, + }, + }, { name: "new entry among others", configToUpdate: v1alpha1.CredentialIssuerStatus{ From 01713c7ce113b89f4373ba71037765eca978843c Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 27 May 2021 17:10:25 -0500 Subject: [PATCH 43/43] Don't reconcile Service ports in impersonatorconfig. These are tricky because a real load balancer controller (e.g., on GKE) will overwrite and set NodePort, so we can't blindly set the desired state of this fields. For now, we will just skip reconciling these. In the future, we could be more clever about merging them together with the current state. Signed-off-by: Margo Crawford --- internal/controller/impersonatorconfig/impersonator_config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 6dfc243c..29e7dfa7 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -538,7 +538,6 @@ func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context updated.Spec.LoadBalancerIP = service.Spec.LoadBalancerIP updated.Spec.Type = service.Spec.Type updated.Spec.Selector = service.Spec.Selector - updated.Spec.Ports = service.Spec.Ports // If our updates didn't change anything, we're done. if equality.Semantic.DeepEqual(existing, updated) {