Merge pull request #626 from vmware-tanzu/credentialissuer-spec-api

Add spec fields to Credentialissuer API to configure impersonation proxy behavior
This commit is contained in:
Matt Moyer 2021-05-27 17:48:45 -05:00 committed by GitHub
commit a39b328778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 3919 additions and 1560 deletions

View File

@ -3,17 +3,23 @@
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
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 +42,91 @@ const (
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
)
// Status of a credential issuer.
// CredentialIssuerSpec describes the intended configuration of the Concierge.
type CredentialIssuerSpec struct {
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
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.
Mode ImpersonationProxyMode `json:"mode"`
// 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 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".
//
// +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.
Strategies []CredentialIssuerStrategy `json:"strategies"`
@ -47,7 +137,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 +150,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 +172,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 +210,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
@ -128,12 +220,18 @@ 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
Spec CredentialIssuerSpec `json:"spec"`
// CredentialIssuerStatus describes the status of the Concierge.
//
// +optional
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"`

View File

@ -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
@ -35,8 +36,73 @@ spec:
type: string
metadata:
type: object
spec:
description: Spec describes the intended configuration of the Concierge.
properties:
impersonationProxy:
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 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:
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 configuration of the Service
provisioned to expose the impersonation proxy to clients.
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
@ -60,8 +126,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

View File

@ -49,8 +49,8 @@ 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") @)
impersonationCACertificateSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-ca-certificate") @)
impersonationSignerSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-signer-ca-certificate") @)
@ -253,3 +253,15 @@ kind: CredentialIssuer
metadata:
name: #@ defaultResourceNameWithSuffix("config")
labels: #@ labels()
spec:
impersonationProxy:
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: #@ 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

View File

@ -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:

View File

@ -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:
****
@ -232,7 +232,8 @@ Describes the configuration status of a Pinniped credential issuer.
| 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,10 +276,27 @@ Describes the configuration status of a Pinniped credential issuer.
[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
Status of a credential issuer.
CredentialIssuerStatus describes the status of the Concierge.
.Appears In:
****
@ -333,6 +351,70 @@ Status of a credential issuer.
|===
[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
.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 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".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"]
==== TokenCredentialRequestAPIInfo

View File

@ -3,17 +3,23 @@
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
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 +42,91 @@ const (
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
)
// Status of a credential issuer.
// CredentialIssuerSpec describes the intended configuration of the Concierge.
type CredentialIssuerSpec struct {
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
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.
Mode ImpersonationProxyMode `json:"mode"`
// 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 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".
//
// +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.
Strategies []CredentialIssuerStrategy `json:"strategies"`
@ -47,7 +137,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 +150,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 +172,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 +210,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
@ -128,12 +220,18 @@ 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
Spec CredentialIssuerSpec `json:"spec"`
// CredentialIssuerStatus describes the status of the Concierge.
//
// +optional
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"`

View File

@ -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,27 @@ 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
if in.ImpersonationProxy != nil {
in, out := &in.ImpersonationProxy, &out.ImpersonationProxy
*out = new(ImpersonationProxySpec)
(*in).DeepCopyInto(*out)
}
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 +201,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

View File

@ -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
@ -35,8 +36,73 @@ spec:
type: string
metadata:
type: object
spec:
description: Spec describes the intended configuration of the Concierge.
properties:
impersonationProxy:
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 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:
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 configuration of the Service
provisioned to expose the impersonation proxy to clients.
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
@ -60,8 +126,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

View File

@ -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:
****
@ -232,7 +232,8 @@ Describes the configuration status of a Pinniped credential issuer.
| 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,10 +276,27 @@ Describes the configuration status of a Pinniped credential issuer.
[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
Status of a credential issuer.
CredentialIssuerStatus describes the status of the Concierge.
.Appears In:
****
@ -333,6 +351,70 @@ Status of a credential issuer.
|===
[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
.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 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".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"]
==== TokenCredentialRequestAPIInfo

View File

@ -3,17 +3,23 @@
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
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 +42,91 @@ const (
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
)
// Status of a credential issuer.
// CredentialIssuerSpec describes the intended configuration of the Concierge.
type CredentialIssuerSpec struct {
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
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.
Mode ImpersonationProxyMode `json:"mode"`
// 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 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".
//
// +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.
Strategies []CredentialIssuerStrategy `json:"strategies"`
@ -47,7 +137,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 +150,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 +172,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 +210,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
@ -128,12 +220,18 @@ 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
Spec CredentialIssuerSpec `json:"spec"`
// CredentialIssuerStatus describes the status of the Concierge.
//
// +optional
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"`

View File

@ -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,27 @@ 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
if in.ImpersonationProxy != nil {
in, out := &in.ImpersonationProxy, &out.ImpersonationProxy
*out = new(ImpersonationProxySpec)
(*in).DeepCopyInto(*out)
}
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 +201,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

View File

@ -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
@ -35,8 +36,73 @@ spec:
type: string
metadata:
type: object
spec:
description: Spec describes the intended configuration of the Concierge.
properties:
impersonationProxy:
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 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:
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 configuration of the Service
provisioned to expose the impersonation proxy to clients.
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
@ -60,8 +126,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

View File

@ -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:
****
@ -232,7 +232,8 @@ Describes the configuration status of a Pinniped credential issuer.
| 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,10 +276,27 @@ Describes the configuration status of a Pinniped credential issuer.
[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
Status of a credential issuer.
CredentialIssuerStatus describes the status of the Concierge.
.Appears In:
****
@ -333,6 +351,70 @@ Status of a credential issuer.
|===
[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
.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 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".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"]
==== TokenCredentialRequestAPIInfo

View File

@ -3,17 +3,23 @@
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
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 +42,91 @@ const (
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
)
// Status of a credential issuer.
// CredentialIssuerSpec describes the intended configuration of the Concierge.
type CredentialIssuerSpec struct {
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
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.
Mode ImpersonationProxyMode `json:"mode"`
// 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 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".
//
// +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.
Strategies []CredentialIssuerStrategy `json:"strategies"`
@ -47,7 +137,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 +150,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 +172,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 +210,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
@ -128,12 +220,18 @@ 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
Spec CredentialIssuerSpec `json:"spec"`
// CredentialIssuerStatus describes the status of the Concierge.
//
// +optional
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"`

View File

@ -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,27 @@ 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
if in.ImpersonationProxy != nil {
in, out := &in.ImpersonationProxy, &out.ImpersonationProxy
*out = new(ImpersonationProxySpec)
(*in).DeepCopyInto(*out)
}
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 +201,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

View File

@ -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
@ -35,8 +36,73 @@ spec:
type: string
metadata:
type: object
spec:
description: Spec describes the intended configuration of the Concierge.
properties:
impersonationProxy:
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 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:
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 configuration of the Service
provisioned to expose the impersonation proxy to clients.
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
@ -60,8 +126,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

View File

@ -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:
****
@ -232,7 +232,8 @@ Describes the configuration status of a Pinniped credential issuer.
| 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,10 +276,27 @@ Describes the configuration status of a Pinniped credential issuer.
[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
Status of a credential issuer.
CredentialIssuerStatus describes the status of the Concierge.
.Appears In:
****
@ -333,6 +351,70 @@ Status of a credential issuer.
|===
[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
.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 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".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"]
==== TokenCredentialRequestAPIInfo

View File

@ -3,17 +3,23 @@
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
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 +42,91 @@ const (
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
)
// Status of a credential issuer.
// CredentialIssuerSpec describes the intended configuration of the Concierge.
type CredentialIssuerSpec struct {
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
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.
Mode ImpersonationProxyMode `json:"mode"`
// 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 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".
//
// +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.
Strategies []CredentialIssuerStrategy `json:"strategies"`
@ -47,7 +137,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 +150,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 +172,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 +210,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
@ -128,12 +220,18 @@ 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
Spec CredentialIssuerSpec `json:"spec"`
// CredentialIssuerStatus describes the status of the Concierge.
//
// +optional
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"`

View File

@ -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,27 @@ 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
if in.ImpersonationProxy != nil {
in, out := &in.ImpersonationProxy, &out.ImpersonationProxy
*out = new(ImpersonationProxySpec)
(*in).DeepCopyInto(*out)
}
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 +201,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

View File

@ -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
@ -35,8 +36,73 @@ spec:
type: string
metadata:
type: object
spec:
description: Spec describes the intended configuration of the Concierge.
properties:
impersonationProxy:
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 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:
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 configuration of the Service
provisioned to expose the impersonation proxy to clients.
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
@ -60,8 +126,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

View File

@ -3,17 +3,23 @@
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
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 +42,91 @@ const (
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
)
// Status of a credential issuer.
// CredentialIssuerSpec describes the intended configuration of the Concierge.
type CredentialIssuerSpec struct {
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
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.
Mode ImpersonationProxyMode `json:"mode"`
// 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 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".
//
// +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.
Strategies []CredentialIssuerStrategy `json:"strategies"`
@ -47,7 +137,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 +150,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 +172,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 +210,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
@ -128,12 +220,18 @@ 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
Spec CredentialIssuerSpec `json:"spec"`
// CredentialIssuerStatus describes the status of the Concierge.
//
// +optional
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"`

View File

@ -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,27 @@ 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
if in.ImpersonationProxy != nil {
in, out := &in.ImpersonationProxy, &out.ImpersonationProxy
*out = new(ImpersonationProxySpec)
(*in).DeepCopyInto(*out)
}
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 +201,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

View File

@ -1,65 +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"
)
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
}

View File

@ -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)
}
})
}
}

View File

@ -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{}

View File

@ -108,12 +108,12 @@ 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")
}
if names.ImpersonationClusterIPService == "" {
missingNames = append(missingNames, "impersonationClusterIPService")
}
if names.ImpersonationTLSCertificateSecret == "" {
missingNames = append(missingNames, "impersonationTLSCertificateSecret")
}

View File

@ -38,13 +38,14 @@ 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
impersonationCACertificateSecret: impersonationCACertificateSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
agentServiceAccount: agentServiceAccount-value
extraName: extraName-value
labels:
myLabelKey1: myLabelValue1
myLabelKey2: myLabelValue2
@ -69,8 +70,8 @@ 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",
ImpersonationCACertificateSecret: "impersonationCACertificateSecret-value",
ImpersonationSignerSecret: "impersonationSignerSecret-value",
@ -96,8 +97,8 @@ 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
impersonationCACertificateSecret: impersonationCACertificateSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
@ -118,8 +119,8 @@ 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",
ImpersonationCACertificateSecret: "impersonationCACertificateSecret-value",
ImpersonationSignerSecret: "impersonationSignerSecret-value",
@ -136,8 +137,8 @@ func TestFromPath(t *testing.T) {
name: "Empty",
yaml: here.Doc(``),
wantError: "validate names: missing required names: servingCertificateSecret, credentialIssuer, " +
"apiService, impersonationConfigMap, impersonationLoadBalancerService, " +
"impersonationTLSCertificateSecret, impersonationCACertificateSecret, " +
"apiService, impersonationLoadBalancerService, " +
"impersonationClusterIPService, impersonationTLSCertificateSecret, impersonationCACertificateSecret, " +
"impersonationSignerSecret, agentServiceAccount",
},
{
@ -147,8 +148,8 @@ 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
impersonationCACertificateSecret: impersonationCACertificateSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
@ -163,8 +164,8 @@ 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
impersonationCACertificateSecret: impersonationCACertificateSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
@ -179,8 +180,8 @@ func TestFromPath(t *testing.T) {
names:
credentialIssuer: pinniped-config
apiService: pinniped-api
impersonationConfigMap: impersonationConfigMap-value
impersonationLoadBalancerService: impersonationLoadBalancerService-value
impersonationClusterIPService: impersonationClusterIPService-value
impersonationTLSCertificateSecret: impersonationTLSCertificateSecret-value
impersonationCACertificateSecret: impersonationCACertificateSecret-value
impersonationSignerSecret: impersonationSignerSecret-value
@ -188,22 +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
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(`
@ -212,7 +197,7 @@ 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
impersonationSignerSecret: impersonationSignerSecret-value
@ -220,6 +205,22 @@ 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
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(`
@ -228,8 +229,8 @@ 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
impersonationSignerSecret: impersonationSignerSecret-value
agentServiceAccount: agentServiceAccount-value
@ -244,8 +245,8 @@ 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
impersonationSignerSecret: impersonationSignerSecret-value
agentServiceAccount: agentServiceAccount-value
@ -260,8 +261,8 @@ 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
impersonationCACertificateSecret: impersonationCACertificateSecret-value
agentServiceAccount: agentServiceAccount-value
@ -277,10 +278,11 @@ func TestFromPath(t *testing.T) {
credentialIssuer: pinniped-config
apiService: pinniped-api
impersonationLoadBalancerService: impersonationLoadBalancerService-value
impersonationClusterIPService: impersonationClusterIPService-value
impersonationSignerSecret: impersonationSignerSecret-value
agentServiceAccount: agentServiceAccount-value
`),
wantError: "validate names: missing required names: impersonationConfigMap, " +
wantError: "validate names: missing required names: " +
"impersonationTLSCertificateSecret, impersonationCACertificateSecret",
},
{
@ -295,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
@ -315,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
@ -335,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
@ -356,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

View File

@ -36,8 +36,8 @@ type NamesConfigSpec struct {
ServingCertificateSecret string `json:"servingCertificateSecret"`
CredentialIssuer string `json:"credentialIssuer"`
APIService string `json:"apiService"`
ImpersonationConfigMap string `json:"impersonationConfigMap"`
ImpersonationLoadBalancerService string `json:"impersonationLoadBalancerService"`
ImpersonationClusterIPService string `json:"impersonationClusterIPService"`
ImpersonationTLSCertificateSecret string `json:"impersonationTLSCertificateSecret"`
ImpersonationCACertificateSecret string `json:"impersonationCACertificateSecret"`
ImpersonationSignerSecret string `json:"impersonationSignerSecret"`

View File

@ -14,19 +14,25 @@ import (
"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"
"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"
"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"
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"
@ -36,7 +42,7 @@ import (
"go.pinniped.dev/internal/controller/issuerconfig"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/dynamiccert"
"go.pinniped.dev/internal/plog"
"go.pinniped.dev/internal/endpointaddr"
)
const (
@ -51,9 +57,9 @@ const (
type impersonatorConfigController struct {
namespace string
configMapResourceName string
credentialIssuerResourceName string
generatedLoadBalancerServiceName string
generatedClusterIPServiceName string
tlsSecretName string
caSecretName string
impersonationSignerSecretName string
@ -61,7 +67,7 @@ type impersonatorConfigController struct {
k8sClient kubernetes.Interface
pinnipedAPIClient pinnipedclientset.Interface
configMapsInformer corev1informers.ConfigMapInformer
credIssuerInformer conciergeconfiginformers.CredentialIssuerInformer
servicesInformer corev1informers.ServiceInformer
secretsInformer corev1informers.SecretInformer
@ -74,20 +80,21 @@ type impersonatorConfigController struct {
serverStopCh chan struct{}
errorCh chan error
tlsServingCertDynamicCertProvider dynamiccert.Private
infoLog logr.Logger
debugLog logr.Logger
}
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,
withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc,
generatedLoadBalancerServiceName string,
generatedClusterIPServiceName string,
tlsSecretName string,
caSecretName string,
labels map[string]string,
@ -95,22 +102,24 @@ 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",
Syncer: &impersonatorConfigController{
namespace: namespace,
configMapResourceName: configMapResourceName,
credentialIssuerResourceName: credentialIssuerResourceName,
generatedLoadBalancerServiceName: generatedLoadBalancerServiceName,
generatedClusterIPServiceName: generatedClusterIPServiceName,
tlsSecretName: tlsSecretName,
caSecretName: caSecretName,
impersonationSignerSecretName: impersonationSignerSecretName,
k8sClient: k8sClient,
pinnipedAPIClient: pinnipedAPIClient,
configMapsInformer: configMapsInformer,
credIssuerInformer: credentialIssuerInformer,
servicesInformer: servicesInformer,
secretsInformer: secretsInformer,
labels: labels,
@ -118,45 +127,48 @@ func NewImpersonatorConfigController(
impersonationSigningCertProvider: impersonationSigningCertProvider,
impersonatorFunc: impersonatorFunc,
tlsServingCertDynamicCertProvider: dynamiccert.NewServingCert("impersonation-proxy-serving-cert"),
infoLog: log.V(2),
debugLog: log.V(4),
},
},
withInformer(
configMapsInformer,
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(configMapResourceName, namespace),
withInformer(credentialIssuerInformer,
pinnipedcontroller.SimpleFilterWithSingletonQueue(func(obj metav1.Object) bool {
return obj.GetName() == credentialIssuerResourceName
}),
controllerlib.InformerOption{},
),
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 ConfigMap that the informer is watching doesn't exist so we can implement
// the default configuration behavior.
withInitialEvent(controllerlib.Key{
Namespace: namespace,
Name: configMapResourceName,
}),
// TODO fix these controller options to make this a singleton queue
)
}
func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error {
plog.Debug("Starting impersonatorConfigController Sync")
c.debugLog.Info("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,
Status: v1alpha1.ErrorStrategyStatus,
Reason: v1alpha1.ErrorDuringSetupStrategyReason,
Reason: strategyReasonForError(err),
Message: err.Error(),
LastUpdateTime: metav1.NewTime(c.clock.Now()),
}
@ -164,20 +176,31 @@ 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")
c.debugLog.Info("successfully finished impersonatorConfigController Sync")
}
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.
@ -186,7 +209,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.
@ -194,10 +217,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
}
@ -212,21 +235,21 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v
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(config) {
if c.shouldHaveImpersonator(impersonationSpec) {
if err = c.ensureImpersonatorIsStarted(syncCtx); err != nil {
return nil, err
}
} 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
}
}
if c.shouldHaveLoadBalancer(config) {
if err = c.ensureLoadBalancerIsStarted(ctx); err != nil {
if c.shouldHaveLoadBalancer(impersonationSpec) {
if err = c.ensureLoadBalancerIsStarted(ctx, impersonationSpec); err != nil {
return nil, err
}
} else {
@ -235,7 +258,17 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v
}
}
nameInfo, err := c.findDesiredTLSCertificateName(config)
if c.shouldHaveClusterIPService(impersonationSpec) {
if err = c.ensureClusterIPServiceIsStarted(ctx, impersonationSpec); err != nil {
return nil, err
}
} else {
if err = c.ensureClusterIPServiceIsStopped(ctx); err != nil {
return nil, err
}
}
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()
@ -243,7 +276,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
}
@ -254,7 +287,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
@ -263,64 +296,55 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v
return credentialIssuerStrategyResult, nil
}
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)
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 {
return nil, fmt.Errorf("could not load CredentialIssuer: spec.impersonationProxy is nil")
}
var config *impersonator.Config
if notFound {
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,
)
// Default service type to LoadBalancer (this is normally already done via CRD defaulting).
if spec.Service.Type == "" {
spec.Service.Type = v1alpha1.ImpersonationProxyServiceTypeLoadBalancer
}
return config, nil
if err := validateCredentialIssuerSpec(spec); err != nil {
return nil, fmt.Errorf("could not load CredentialIssuer spec.impersonationProxy: %w", err)
}
c.debugLog.Info("read impersonation proxy config", "credentialIssuer", c.credentialIssuerResourceName)
return spec, 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) 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)
}
func (c *impersonatorConfigController) updateStrategy(ctx context.Context, strategy *v1alpha1.CredentialIssuerStrategy) error {
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)
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
@ -367,7 +391,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,
@ -402,7 +426,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
@ -416,14 +440,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{
@ -435,26 +452,21 @@ 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,
},
}
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, &loadBalancer)
}
func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.Context) error {
running, err := c.loadBalancerExists()
running, err := c.serviceExists(c.generatedLoadBalancerServiceName)
if err != nil {
return err
}
@ -462,12 +474,82 @@ 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{})
}
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,
},
}
return c.createOrUpdateService(ctx, &clusterIP)
}
func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx context.Context) error {
running, err := c.serviceExists(c.generatedClusterIPServiceName)
if err != nil {
return err
}
if !running {
return nil
}
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{})
}
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
}
// 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
// If our updates didn't change anything, we're done.
if equality.Semantic.DeepEqual(existing, updated) {
return nil
}
// 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
}
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)
@ -494,10 +576,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)
@ -507,10 +589,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)
}
@ -520,9 +602,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)
}
@ -550,15 +633,15 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc
actualIPs := actualCertFromSecret.IPAddresses
actualHostnames := actualCertFromSecret.DNSNames
plog.Info("Checking TLS certificate names",
"desiredIP", nameInfo.selectedIP,
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.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
}
@ -569,8 +652,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 {
@ -592,7 +680,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
}
@ -650,9 +738,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
}
@ -660,21 +748,23 @@ 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) {
if config.ExternalEndpoint != "" {
return c.findTLSCertificateNameFromEndpointConfig(config), nil
} else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP {
return c.findTLSCertificateNameFromClusterIPService()
}
return c.findTLSCertificateNameFromLoadBalancer()
}
func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig(config *impersonator.Config) *certNameInfo {
endpointMaybeWithPort := config.Endpoint
endpointWithoutPort := strings.Split(endpointMaybeWithPort, ":")[0]
parsedAsIP := net.ParseIP(endpointWithoutPort)
if parsedAsIP != nil {
return &certNameInfo{ready: true, selectedIP: parsedAsIP, clientEndpoint: endpointMaybeWithPort}
func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig(config *v1alpha1.ImpersonationProxySpec) *certNameInfo {
addr, _ := endpointaddr.Parse(config.ExternalEndpoint, 443)
endpoint := strings.TrimSuffix(addr.Endpoint(), ":443")
if ip := net.ParseIP(addr.Host); ip != nil {
return &certNameInfo{ready: true, selectedIPs: []net.IP{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) {
@ -689,9 +779,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 {
@ -704,22 +794,45 @@ 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
}
}
return nil, fmt.Errorf("could not find valid IP addresses or hostnames from load balancer %s/%s", c.namespace, lb.Name)
}
func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, ca *certauthority.CA, ip net.IP, hostname string) (*v1.Secret, error) {
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
ips := clusterIP.Spec.ClusterIPs
if ip != "" {
// 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, 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 {
@ -744,11 +857,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{})
}
@ -761,10 +874,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
}
@ -777,9 +890,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 {
@ -815,20 +928,20 @@ 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.debugLog.Info("clearing credential signing certificate for impersonation proxy")
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{
@ -871,3 +984,46 @@ func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, conf
}
}
}
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 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 spec.ExternalEndpoint != "" {
if _, err := endpointaddr.Parse(spec.ExternalEndpoint, 443); err != nil {
return fmt.Errorf("invalid ExternalEndpoint %q: %w", spec.ExternalEndpoint, err)
}
}
return nil
}

View File

@ -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,
},
}
}

View File

@ -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{}))
}

View File

@ -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

View File

@ -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.
@ -58,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)
}
@ -91,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)
}

View File

@ -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{

View File

@ -252,16 +252,15 @@ 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,
controllerlib.WithInitialEvent,
c.NamesConfig.ImpersonationLoadBalancerService,
c.NamesConfig.ImpersonationClusterIPService,
c.NamesConfig.ImpersonationTLSCertificateSecret,
c.NamesConfig.ImpersonationCACertificateSecret,
c.Labels,
@ -269,6 +268,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
impersonator.New,
c.NamesConfig.ImpersonationSignerSecret,
c.ImpersonationSigningCertProvider,
klogr.New(),
),
singletonWorker,
).

View File

@ -18,6 +18,8 @@ You should have a [supported Kubernetes cluster]({{< ref "../reference/supported
- `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
1. Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number and use it to replace the version number in the URL below.

View File

@ -21,6 +21,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"sync"
"testing"
@ -51,13 +52,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"
@ -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,18 +136,13 @@ 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 {
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,
@ -156,36 +154,38 @@ 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
}
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{}))
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)
}
// 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.
@ -200,6 +200,15 @@ 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,
},
},
})
// 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.
@ -221,10 +230,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 +256,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
@ -376,6 +385,24 @@ 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 {
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()
@ -1181,18 +1208,47 @@ 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 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)
anonymousClient := newAnonymousImpersonationProxyClientWithProxy(t, newImpersonationProxyURL, newImpersonationProxyCACertPEM, nil).PinnipedConcierge
refreshedCredentials := refreshCredentialHelper(t, anonymousClient)
client := newImpersonationProxyClientWithCredentialsAndProxy(t, refreshedCredentials, newImpersonationProxyURL, newImpersonationProxyCACertPEM, nil).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
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 +1506,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,8 +1532,16 @@ 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 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 {
@ -1492,6 +1556,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"
}
@ -1643,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()

View File

@ -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{},
)