Merge pull request #158 from vmware-tanzu/label_every_resource

Custom labels can to be applied to all k8s resources created by Pinniped
This commit is contained in:
Ryan Richard 2020-10-15 14:02:29 -07:00 committed by GitHub
commit 08659a6583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 679 additions and 262 deletions

View File

@ -14,7 +14,6 @@ RUN go mod download
COPY generated ./generated COPY generated ./generated
COPY cmd ./cmd COPY cmd ./cmd
COPY internal ./internal COPY internal ./internal
COPY pkg ./pkg
COPY tools ./tools COPY tools ./tools
COPY hack ./hack COPY hack ./hack

View File

@ -310,6 +310,9 @@ func startControllers(
apicerts.NewCertsManagerController( apicerts.NewCertsManagerController(
namespace, namespace,
certsSecretResourceName, certsSecretResourceName,
map[string]string{
"app": "local-user-authenticator",
},
kubeClient, kubeClient,
kubeInformers.Core().V1().Secrets(), kubeInformers.Core().V1().Secrets(),
controllerlib.WithInformer, controllerlib.WithInformer,

View File

@ -23,6 +23,7 @@ import (
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned" pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
pinnipedinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions" pinnipedinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions"
"go.pinniped.dev/internal/config/supervisor"
"go.pinniped.dev/internal/controller/supervisorconfig" "go.pinniped.dev/internal/controller/supervisorconfig"
"go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/downward" "go.pinniped.dev/internal/downward"
@ -63,6 +64,7 @@ func waitForSignal() os.Signal {
func startControllers( func startControllers(
ctx context.Context, ctx context.Context,
cfg *supervisor.Config,
issuerProvider *manager.Manager, issuerProvider *manager.Manager,
kubeClient kubernetes.Interface, kubeClient kubernetes.Interface,
pinnipedClient pinnipedclientset.Interface, pinnipedClient pinnipedclientset.Interface,
@ -84,6 +86,7 @@ func startControllers(
). ).
WithController( WithController(
supervisorconfig.NewJWKSController( supervisorconfig.NewJWKSController(
cfg.Labels,
kubeClient, kubeClient,
pinnipedClient, pinnipedClient,
kubeInformers.Core().V1().Secrets(), kubeInformers.Core().V1().Secrets(),
@ -120,7 +123,7 @@ func newClients() (kubernetes.Interface, pinnipedclientset.Interface, error) {
return kubeClient, pinnipedClient, nil return kubeClient, pinnipedClient, nil
} }
func run(serverInstallationNamespace string) error { func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -142,7 +145,7 @@ func run(serverInstallationNamespace string) error {
) )
oidProvidersManager := manager.NewManager(http.NotFoundHandler()) oidProvidersManager := manager.NewManager(http.NotFoundHandler())
startControllers(ctx, oidProvidersManager, kubeClient, pinnipedClient, kubeInformers, pinnipedInformers) startControllers(ctx, cfg, oidProvidersManager, kubeClient, pinnipedClient, kubeInformers, pinnipedInformers)
//nolint: gosec // Intentionally binding to all network interfaces. //nolint: gosec // Intentionally binding to all network interfaces.
l, err := net.Listen("tcp", ":80") l, err := net.Listen("tcp", ":80")
@ -173,7 +176,13 @@ func main() {
klog.Fatal(fmt.Errorf("could not read pod metadata: %w", err)) klog.Fatal(fmt.Errorf("could not read pod metadata: %w", err))
} }
if err := run(podInfo.Namespace); err != nil { // Read the server config file.
cfg, err := supervisor.FromPath(os.Args[2])
if err != nil {
klog.Fatal(fmt.Errorf("could not load config: %w", err))
}
if err := run(podInfo.Namespace, cfg); err != nil {
klog.Fatal(err) klog.Fatal(err)
} }
} }

View File

@ -2,28 +2,31 @@
#! SPDX-License-Identifier: Apache-2.0 #! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
#@ load("@ytt:json", "json")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ if not data.values.into_namespace:
--- ---
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: #@ data.values.namespace name: #@ data.values.namespace
labels: labels: #@ labels()
name: #@ data.values.namespace #@ end
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: #@ labels()
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: #@ data.values.app_name + "-config" name: #@ defaultResourceNameWithSuffix("config")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
data: data:
#! If names.apiService is changed in this ConfigMap, must also change name of the ClusterIP Service resource below. #! If names.apiService is changed in this ConfigMap, must also change name of the ClusterIP Service resource below.
#@yaml/text-templated-strings #@yaml/text-templated-strings
@ -35,11 +38,12 @@ data:
durationSeconds: (@= str(data.values.api_serving_certificate_duration_seconds) @) durationSeconds: (@= str(data.values.api_serving_certificate_duration_seconds) @)
renewBeforeSeconds: (@= str(data.values.api_serving_certificate_renew_before_seconds) @) renewBeforeSeconds: (@= str(data.values.api_serving_certificate_renew_before_seconds) @)
names: names:
servingCertificateSecret: (@= data.values.app_name + "-api-tls-serving-certificate" @) servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @)
credentialIssuerConfig: (@= data.values.app_name + "-config" @) credentialIssuerConfig: (@= defaultResourceNameWithSuffix("config") @)
apiService: (@= data.values.app_name + "-api" @) apiService: (@= defaultResourceNameWithSuffix("api") @)
labels: (@= json.encode(labels()).rstrip() @)
kubeCertAgent: kubeCertAgent:
namePrefix: (@= data.values.app_name + "-kube-cert-agent-" @) namePrefix: (@= defaultResourceNameWithSuffix("kube-cert-agent-") @)
(@ if data.values.kube_cert_agent_image: @) (@ if data.values.kube_cert_agent_image: @)
image: (@= data.values.kube_cert_agent_image @) image: (@= data.values.kube_cert_agent_image @)
(@ else: @) (@ else: @)
@ -59,9 +63,8 @@ apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: image-pull-secret name: image-pull-secret
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson
data: data:
.dockerconfigjson: #@ data.values.image_pull_dockerconfigjson .dockerconfigjson: #@ data.values.image_pull_dockerconfigjson
@ -70,29 +73,26 @@ data:
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
spec: spec:
replicas: #@ data.values.replicas replicas: #@ data.values.replicas
selector: selector:
matchLabels: matchLabels: #@ defaultLabel()
app: #@ data.values.app_name
template: template:
metadata: metadata:
labels: labels: #@ defaultLabel()
app: #@ data.values.app_name
annotations: annotations:
scheduler.alpha.kubernetes.io/critical-pod: "" scheduler.alpha.kubernetes.io/critical-pod: ""
spec: spec:
serviceAccountName: #@ data.values.app_name serviceAccountName: #@ defaultResourceName()
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "": #@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets: imagePullSecrets:
- name: image-pull-secret - name: image-pull-secret
#@ end #@ end
containers: containers:
- name: pinniped - name: #@ defaultResourceName()
#@ if data.values.image_digest: #@ if data.values.image_digest:
image: #@ data.values.image_repo + "@" + data.values.image_digest image: #@ data.values.image_repo + "@" + data.values.image_digest
#@ else: #@ else:
@ -131,7 +131,7 @@ spec:
volumes: volumes:
- name: config-volume - name: config-volume
configMap: configMap:
name: #@ data.values.app_name + "-config" name: #@ defaultResourceNameWithSuffix("config")
- name: podinfo - name: podinfo
downwardAPI: downwardAPI:
items: items:
@ -157,22 +157,19 @@ spec:
- weight: 50 - weight: 50
podAffinityTerm: podAffinityTerm:
labelSelector: labelSelector:
matchLabels: matchLabels: #@ defaultLabel()
app: #@ data.values.app_name
topologyKey: kubernetes.io/hostname topologyKey: kubernetes.io/hostname
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
#! If name is changed, must also change names.apiService in the ConfigMap above and spec.service.name in the APIService below. #! If name is changed, must also change names.apiService in the ConfigMap above and spec.service.name in the APIService below.
name: #@ data.values.app_name + "-api" name: #@ defaultResourceNameWithSuffix("api")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
spec: spec:
type: ClusterIP type: ClusterIP
selector: selector: #@ defaultLabel()
app: #@ data.values.app_name
ports: ports:
- protocol: TCP - protocol: TCP
port: 443 port: 443
@ -182,8 +179,7 @@ apiVersion: apiregistration.k8s.io/v1
kind: APIService kind: APIService
metadata: metadata:
name: v1alpha1.login.pinniped.dev name: v1alpha1.login.pinniped.dev
labels: labels: #@ labels()
app: #@ data.values.app_name
spec: spec:
version: v1alpha1 version: v1alpha1
group: login.pinniped.dev group: login.pinniped.dev
@ -191,6 +187,6 @@ spec:
versionPriority: 10 versionPriority: 10
#! caBundle: Do not include this key here. Starts out null, will be updated/owned by the golang code. #! caBundle: Do not include this key here. Starts out null, will be updated/owned by the golang code.
service: service:
name: #@ data.values.app_name + "-api" name: #@ defaultResourceNameWithSuffix("api")
namespace: #@ data.values.namespace namespace: #@ namespace()
port: 443 port: 443

View File

@ -0,0 +1,30 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("@ytt:template", "template")
#@ def defaultResourceName():
#@ return data.values.app_name
#@ end
#@ def defaultResourceNameWithSuffix(suffix):
#@ return data.values.app_name + "-" + suffix
#@ end
#@ def namespace():
#@ if data.values.into_namespace:
#@ return data.values.into_namespace
#@ else:
#@ return data.values.namespace
#@ end
#@ end
#@ def defaultLabel():
app: #@ data.values.app_name
#@ end
#@ def labels():
_: #@ template.replace(defaultLabel())
_: #@ template.replace(data.values.custom_labels)
#@ end

View File

@ -2,35 +2,38 @@
#! SPDX-License-Identifier: Apache-2.0 #! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#! Give permission to various cluster-scoped objects #! Give permission to various cluster-scoped objects
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: #@ data.values.app_name + "-aggregated-api-server" name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
labels: #@ labels()
rules: rules:
- apiGroups: [""] - apiGroups: [ "" ]
resources: [namespaces] resources: [ namespaces ]
verbs: [get, list, watch] verbs: [ get, list, watch ]
- apiGroups: [apiregistration.k8s.io] - apiGroups: [ apiregistration.k8s.io ]
resources: [apiservices] resources: [ apiservices ]
verbs: [create, get, list, patch, update, watch] verbs: [ create, get, list, patch, update, watch ]
- apiGroups: [admissionregistration.k8s.io] - apiGroups: [ admissionregistration.k8s.io ]
resources: [validatingwebhookconfigurations, mutatingwebhookconfigurations] resources: [ validatingwebhookconfigurations, mutatingwebhookconfigurations ]
verbs: [get, list, watch] verbs: [ get, list, watch ]
--- ---
kind: ClusterRoleBinding kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name + "-aggregated-api-server" name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
labels: #@ labels()
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
roleRef: roleRef:
kind: ClusterRole kind: ClusterRole
name: #@ data.values.app_name + "-aggregated-api-server" name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
#! Give permission to various objects within the app's own namespace #! Give permission to various objects within the app's own namespace
@ -38,39 +41,41 @@ roleRef:
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
metadata: metadata:
name: #@ data.values.app_name + "-aggregated-api-server" name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: #@ labels()
rules: rules:
- apiGroups: [""] - apiGroups: [ "" ]
resources: [services] resources: [ services ]
verbs: [create, get, list, patch, update, watch] verbs: [ create, get, list, patch, update, watch ]
- apiGroups: [""] - apiGroups: [ "" ]
resources: [secrets] resources: [ secrets ]
verbs: [create, get, list, patch, update, watch, delete] verbs: [ create, get, list, patch, update, watch, delete ]
#! We need to be able to CRUD pods in our namespace so we can reconcile the kube-cert-agent pods. #! We need to be able to CRUD pods in our namespace so we can reconcile the kube-cert-agent pods.
- apiGroups: [""] - apiGroups: [ "" ]
resources: [pods] resources: [ pods ]
verbs: [create, get, list, patch, update, watch, delete] verbs: [ create, get, list, patch, update, watch, delete ]
#! We need to be able to exec into pods in our namespace so we can grab the API server's private key #! We need to be able to exec into pods in our namespace so we can grab the API server's private key
- apiGroups: [""] - apiGroups: [ "" ]
resources: [pods/exec] resources: [ pods/exec ]
verbs: [create] verbs: [ create ]
- apiGroups: [config.pinniped.dev, idp.pinniped.dev] - apiGroups: [ config.pinniped.dev, idp.pinniped.dev ]
resources: ["*"] resources: [ "*" ]
verbs: [create, get, list, update, watch] verbs: [ create, get, list, update, watch ]
--- ---
kind: RoleBinding kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name + "-aggregated-api-server" name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: #@ labels()
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
roleRef: roleRef:
kind: Role kind: Role
name: #@ data.values.app_name + "-aggregated-api-server" name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
#! Give permission to read pods in the kube-system namespace so we can find the API server's private key #! Give permission to read pods in the kube-system namespace so we can find the API server's private key
@ -78,25 +83,27 @@ roleRef:
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
metadata: metadata:
name: #@ data.values.app_name + "-kube-system-pod-read" name: #@ defaultResourceNameWithSuffix("kube-system-pod-read")
namespace: kube-system namespace: kube-system
labels: #@ labels()
rules: rules:
- apiGroups: [""] - apiGroups: [ "" ]
resources: [pods] resources: [ pods ]
verbs: [get, list, watch] verbs: [ get, list, watch ]
--- ---
kind: RoleBinding kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name + "-kube-system-pod-read" name: #@ defaultResourceNameWithSuffix("kube-system-pod-read")
namespace: kube-system namespace: kube-system
labels: #@ labels()
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
roleRef: roleRef:
kind: Role kind: Role
name: #@ data.values.app_name + "-kube-system-pod-read" name: #@ defaultResourceNameWithSuffix("kube-system-pod-read")
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
#! Allow both authenticated and unauthenticated TokenCredentialRequests (i.e. allow all requests) #! Allow both authenticated and unauthenticated TokenCredentialRequests (i.e. allow all requests)
@ -104,16 +111,18 @@ roleRef:
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: #@ data.values.app_name + "-create-token-credential-requests" name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
labels: #@ labels()
rules: rules:
- apiGroups: [login.pinniped.dev] - apiGroups: [ login.pinniped.dev ]
resources: [tokencredentialrequests] resources: [ tokencredentialrequests ]
verbs: [create] verbs: [ create ]
--- ---
kind: ClusterRoleBinding kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name + "-create-token-credential-requests" name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
labels: #@ labels()
subjects: subjects:
- kind: Group - kind: Group
name: system:authenticated name: system:authenticated
@ -123,7 +132,7 @@ subjects:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
roleRef: roleRef:
kind: ClusterRole kind: ClusterRole
name: #@ data.values.app_name + "-create-token-credential-requests" name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
#! Give permissions for subjectaccessreviews, tokenreview that is needed by aggregated api servers #! Give permissions for subjectaccessreviews, tokenreview that is needed by aggregated api servers
@ -131,12 +140,13 @@ roleRef:
kind: ClusterRoleBinding kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: #@ labels()
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
roleRef: roleRef:
kind: ClusterRole kind: ClusterRole
name: system:auth-delegator name: system:auth-delegator
@ -147,12 +157,13 @@ roleRef:
kind: RoleBinding kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name + "-extension-apiserver-authentication-reader" name: #@ defaultResourceNameWithSuffix("extension-apiserver-authentication-reader")
namespace: kube-system namespace: kube-system
labels: #@ labels()
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
roleRef: roleRef:
kind: Role kind: Role
name: extension-apiserver-authentication-reader name: extension-apiserver-authentication-reader
@ -163,23 +174,25 @@ roleRef:
kind: Role kind: Role
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name + "-cluster-info-lister-watcher" name: #@ defaultResourceNameWithSuffix("cluster-info-lister-watcher")
namespace: kube-public namespace: kube-public
labels: #@ labels()
rules: rules:
- apiGroups: [""] - apiGroups: [ "" ]
resources: [configmaps] resources: [ configmaps ]
verbs: [list, watch] verbs: [ list, watch ]
--- ---
kind: RoleBinding kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name + "-cluster-info-lister-watcher" name: #@ defaultResourceNameWithSuffix("cluster-info-lister-watcher")
namespace: kube-public namespace: kube-public
labels: #@ labels()
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
roleRef: roleRef:
kind: Role kind: Role
name: #@ data.values.app_name + "-cluster-info-lister-watcher" name: #@ defaultResourceNameWithSuffix("cluster-info-lister-watcher")
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io

View File

@ -5,7 +5,21 @@
--- ---
app_name: pinniped-concierge app_name: pinniped-concierge
#! Creates a new namespace statically in yaml with the given name and installs the app into that namespace.
namespace: pinniped-concierge namespace: pinniped-concierge
#! If specified, assumes that a namespace of the given name already exists and installs the app into that namespace.
#! If both `namespace` and `into_namespace` are specified, then only `into_namespace` is used.
into_namespace: #! e.g. my-preexisting-namespace
#! All resources created statically by yaml at install-time and all resources created dynamically
#! by controllers at runtime will be labelled with `app: $app_name` and also with the labels
#! specified here. The value of `custom_labels` must be a map of string keys to string values.
#! The app can be uninstalled either by:
#! 1. Deleting the static install-time yaml resources including the static namespace, which will cascade and also delete
#! resources that were dynamically created by controllers at runtime
#! 2. Or, deleting all resources by label, which does not assume that there was a static install-time yaml namespace.
custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLabelName: otherCustomLabelValue}
#! Specify how many replicas of the Pinniped server to run. #! Specify how many replicas of the Pinniped server to run.
replicas: 2 replicas: 2
@ -20,7 +34,7 @@ image_tag: latest
#! By default, the same image specified for image_repo/image_digest/image_tag will be re-used. #! By default, the same image specified for image_repo/image_digest/image_tag will be re-used.
kube_cert_agent_image: kube_cert_agent_image:
#! Specifies a secret to be used when pulling the above container image. #! Specifies a secret to be used when pulling the above `image_repo` container image.
#! Can be used when the above image_repo is a private registry. #! Can be used when the above image_repo is a private registry.
#! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]' #! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]'
#! Optional. #! Optional.

View File

@ -0,0 +1,17 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:overlay", "overlay")
#@ load("helpers.lib.yaml", "labels")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"credentialissuerconfigs.config.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"webhookidentityproviders.idp.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()

View File

@ -9,7 +9,7 @@ image_repo: docker.io/getpinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8 image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest image_tag: latest
#! Specifies a secret to be used when pulling the above container image. #! Specifies a secret to be used when pulling the above `image_repo` container image.
#! Can be used when the above image_repo is a private registry. #! Can be used when the above image_repo is a private registry.
#! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]' #! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]'
#! Optional. #! Optional.

View File

@ -2,42 +2,43 @@
#! SPDX-License-Identifier: Apache-2.0 #! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
#@ load("@ytt:json", "json")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ if not data.values.into_namespace:
--- ---
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: #@ data.values.namespace name: #@ data.values.namespace
labels: labels: #@ labels()
name: #@ data.values.namespace #@ end
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: #@ labels()
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: #@ data.values.app_name + "-static-config" name: #@ defaultResourceNameWithSuffix("static-config")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
data: data:
#@yaml/text-templated-strings #@yaml/text-templated-strings
pinniped.yaml: | pinniped.yaml: |
names: labels: (@= json.encode(labels()).rstrip() @)
dynamicConfigMap: (@= data.values.app_name + "-dynamic-config" @)
--- ---
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "": #@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: image-pull-secret name: image-pull-secret
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson
data: data:
.dockerconfigjson: #@ data.values.image_pull_dockerconfigjson .dockerconfigjson: #@ data.values.image_pull_dockerconfigjson
@ -46,27 +47,24 @@ data:
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
spec: spec:
replicas: #@ data.values.replicas replicas: #@ data.values.replicas
selector: selector:
matchLabels: matchLabels: #@ defaultLabel()
app: #@ data.values.app_name
template: template:
metadata: metadata:
labels: labels: #@ defaultLabel()
app: #@ data.values.app_name
spec: spec:
serviceAccountName: #@ data.values.app_name serviceAccountName: #@ defaultResourceName()
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "": #@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets: imagePullSecrets:
- name: image-pull-secret - name: image-pull-secret
#@ end #@ end
containers: containers:
- name: pinniped-supervisor - name: #@ defaultResourceName()
#@ if data.values.image_digest: #@ if data.values.image_digest:
image: #@ data.values.image_repo + "@" + data.values.image_digest image: #@ data.values.image_repo + "@" + data.values.image_digest
#@ else: #@ else:
@ -89,7 +87,7 @@ spec:
volumes: volumes:
- name: config-volume - name: config-volume
configMap: configMap:
name: #@ data.values.app_name + "-static-config" name: #@ defaultResourceNameWithSuffix("static-config")
- name: podinfo - name: podinfo
downwardAPI: downwardAPI:
items: items:
@ -107,6 +105,5 @@ spec:
- weight: 50 - weight: 50
podAffinityTerm: podAffinityTerm:
labelSelector: labelSelector:
matchLabels: matchLabels: #@ defaultLabel()
app: #@ data.values.app_name
topologyKey: kubernetes.io/hostname topologyKey: kubernetes.io/hostname

View File

@ -0,0 +1,30 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("@ytt:template", "template")
#@ def defaultResourceName():
#@ return data.values.app_name
#@ end
#@ def defaultResourceNameWithSuffix(suffix):
#@ return data.values.app_name + "-" + suffix
#@ end
#@ def namespace():
#@ if data.values.into_namespace:
#@ return data.values.into_namespace
#@ else:
#@ return data.values.namespace
#@ end
#@ end
#@ def defaultLabel():
app: #@ data.values.app_name
#@ end
#@ def labels():
_: #@ template.replace(defaultLabel())
_: #@ template.replace(data.values.custom_labels)
#@ end

View File

@ -2,16 +2,16 @@
#! SPDX-License-Identifier: Apache-2.0 #! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#! Give permission to various objects within the app's own namespace #! Give permission to various objects within the app's own namespace
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
metadata: metadata:
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
rules: rules:
- apiGroups: [""] - apiGroups: [""]
resources: [secrets] resources: [secrets]
@ -23,15 +23,14 @@ rules:
kind: RoleBinding kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: #@ data.values.app_name name: #@ defaultResourceName()
namespace: #@ data.values.namespace namespace: #@ namespace()
roleRef: roleRef:
kind: Role kind: Role
name: #@ data.values.app_name name: #@ defaultResourceName()
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io

View File

@ -1,14 +1,17 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ if data.values.service_nodeport_port: #@ if data.values.service_nodeport_port:
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: #@ data.values.app_name + "-nodeport" name: #@ defaultResourceNameWithSuffix("nodeport")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
spec: spec:
type: NodePort type: NodePort
selector: selector:
@ -25,14 +28,12 @@ spec:
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: #@ data.values.app_name + "-clusterip" name: #@ defaultResourceNameWithSuffix("clusterip")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
spec: spec:
type: ClusterIP type: ClusterIP
selector: selector: #@ defaultLabel()
app: #@ data.values.app_name
ports: ports:
- protocol: TCP - protocol: TCP
port: #@ data.values.service_clusterip_port port: #@ data.values.service_clusterip_port
@ -44,14 +45,12 @@ spec:
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: #@ data.values.app_name + "-loadbalancer" name: #@ defaultResourceNameWithSuffix("loadbalancer")
namespace: #@ data.values.namespace namespace: #@ namespace()
labels: labels: #@ labels()
app: #@ data.values.app_name
spec: spec:
type: LoadBalancer type: LoadBalancer
selector: selector: #@ defaultLabel()
app: #@ data.values.app_name
ports: ports:
- protocol: TCP - protocol: TCP
port: #@ data.values.service_loadbalancer_port port: #@ data.values.service_loadbalancer_port

View File

@ -5,7 +5,21 @@
--- ---
app_name: pinniped-supervisor app_name: pinniped-supervisor
#! Creates a new namespace statically in yaml with the given name and installs the app into that namespace.
namespace: pinniped-supervisor namespace: pinniped-supervisor
#! If specified, assumes that a namespace of the given name already exists and installs the app into that namespace.
#! If both `namespace` and `into_namespace` are specified, then only `into_namespace` is used.
into_namespace: #! e.g. my-preexisting-namespace
#! All resources created statically by yaml at install-time and all resources created dynamically
#! by controllers at runtime will be labelled with `app: $app_name` and also with the labels
#! specified here. The value of `custom_labels` must be a map of string keys to string values.
#! The app can be uninstalled either by:
#! 1. Deleting the static install-time yaml resources including the static namespace, which will cascade and also delete
#! resources that were dynamically created by controllers at runtime
#! 2. Or, deleting all resources by label, which does not assume that there was a static install-time yaml namespace.
custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLabelName: otherCustomLabelValue}
#! Specify how many replicas of the Pinniped server to run. #! Specify how many replicas of the Pinniped server to run.
replicas: 2 replicas: 2
@ -15,7 +29,7 @@ image_repo: docker.io/getpinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8 image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest image_tag: latest
#! Specifies a secret to be used when pulling the above container image. #! Specifies a secret to be used when pulling the above `image_repo` container image.
#! Can be used when the above image_repo is a private registry. #! Can be used when the above image_repo is a private registry.
#! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]' #! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]'
#! Optional. #! Optional.

View File

@ -0,0 +1,11 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:overlay", "overlay")
#@ load("helpers.lib.yaml", "labels")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"oidcproviderconfigs.config.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()

View File

@ -94,6 +94,7 @@ k8s_yaml(local([
'--data-value', 'image_tag=tilt-dev', '--data-value', 'image_tag=tilt-dev',
'--data-value-yaml', 'replicas=1', '--data-value-yaml', 'replicas=1',
'--data-value-yaml', 'service_nodeport_port=31234', '--data-value-yaml', 'service_nodeport_port=31234',
'--data-value-yaml', 'custom_labels={mySupervisorCustomLabelName: mySupervisorCustomLabelValue}',
])) ]))
# Tell tilt to watch all of those files for changes. # Tell tilt to watch all of those files for changes.
watch_file('../../../deploy/supervisor') watch_file('../../../deploy/supervisor')
@ -135,7 +136,8 @@ k8s_yaml(local([
'--data-value image_tag=tilt-dev ' + '--data-value image_tag=tilt-dev ' +
'--data-value kube_cert_agent_image=debian:10.5-slim ' + '--data-value kube_cert_agent_image=debian:10.5-slim ' +
'--data-value discovery_url=$(TERM=dumb kubectl cluster-info | awk \'/Kubernetes master/ {print $NF}\') ' + '--data-value discovery_url=$(TERM=dumb kubectl cluster-info | awk \'/Kubernetes master/ {print $NF}\') ' +
'--data-value-yaml replicas=1', '--data-value-yaml replicas=1 ' +
'--data-value-yaml "custom_labels={myConciergeCustomLabelName: myConciergeCustomLabelValue}"'
])) ]))
# Tell tilt to watch all of those files for changes. # Tell tilt to watch all of those files for changes.
watch_file('../../../deploy/concierge') watch_file('../../../deploy/concierge')

View File

@ -212,6 +212,7 @@ kubectl create secret generic "$test_username" \
# #
supervisor_app_name="pinniped-supervisor" supervisor_app_name="pinniped-supervisor"
supervisor_namespace="supervisor" supervisor_namespace="supervisor"
supervisor_custom_labels="{mySupervisorCustomLabelName: mySupervisorCustomLabelValue}"
if ! tilt_mode; then if ! tilt_mode; then
pushd deploy/supervisor >/dev/null pushd deploy/supervisor >/dev/null
@ -222,6 +223,7 @@ if ! tilt_mode; then
--data-value "namespace=$supervisor_namespace" \ --data-value "namespace=$supervisor_namespace" \
--data-value "image_repo=$registry_repo" \ --data-value "image_repo=$registry_repo" \
--data-value "image_tag=$tag" \ --data-value "image_tag=$tag" \
--data-value-yaml "custom_labels=$supervisor_custom_labels" \
--data-value-yaml 'service_nodeport_port=31234' >"$manifest" --data-value-yaml 'service_nodeport_port=31234' >"$manifest"
kapp deploy --yes --app "$supervisor_app_name" --diff-changes --file "$manifest" kapp deploy --yes --app "$supervisor_app_name" --diff-changes --file "$manifest"
@ -230,21 +232,23 @@ if ! tilt_mode; then
fi fi
# #
# Deploy Pinniped # Deploy the Pinniped Concierge
# #
concierge_app_name="pinniped-concierge" concierge_app_name="pinniped-concierge"
concierge_namespace="concierge" concierge_namespace="concierge"
webhook_url="https://local-user-authenticator.local-user-authenticator.svc/authenticate" webhook_url="https://local-user-authenticator.local-user-authenticator.svc/authenticate"
webhook_ca_bundle="$(kubectl get secret local-user-authenticator-tls-serving-certificate --namespace local-user-authenticator -o 'jsonpath={.data.caCertificate}')" webhook_ca_bundle="$(kubectl get secret local-user-authenticator-tls-serving-certificate --namespace local-user-authenticator -o 'jsonpath={.data.caCertificate}')"
discovery_url="$(TERM=dumb kubectl cluster-info | awk '/Kubernetes master/ {print $NF}')" discovery_url="$(TERM=dumb kubectl cluster-info | awk '/Kubernetes master/ {print $NF}')"
concierge_custom_labels="{myConciergeCustomLabelName: myConciergeCustomLabelValue}"
if ! tilt_mode; then if ! tilt_mode; then
pushd deploy/concierge >/dev/null pushd deploy/concierge >/dev/null
log_note "Deploying the Pinniped app to the cluster..." log_note "Deploying the Pinniped Concierge app to the cluster..."
ytt --file . \ ytt --file . \
--data-value "app_name=$concierge_app_name" \ --data-value "app_name=$concierge_app_name" \
--data-value "namespace=$concierge_namespace" \ --data-value "namespace=$concierge_namespace" \
--data-value-yaml "custom_labels=$concierge_custom_labels" \
--data-value "image_repo=$registry_repo" \ --data-value "image_repo=$registry_repo" \
--data-value "image_tag=$tag" \ --data-value "image_tag=$tag" \
--data-value "discovery_url=$discovery_url" >"$manifest" --data-value "discovery_url=$discovery_url" >"$manifest"
@ -264,6 +268,7 @@ cat <<EOF >/tmp/integration-test-env
# The following env vars should be set before running 'go test -v -count 1 ./test/integration' # The following env vars should be set before running 'go test -v -count 1 ./test/integration'
export PINNIPED_TEST_CONCIERGE_NAMESPACE=${concierge_namespace} export PINNIPED_TEST_CONCIERGE_NAMESPACE=${concierge_namespace}
export PINNIPED_TEST_CONCIERGE_APP_NAME=${concierge_app_name} export PINNIPED_TEST_CONCIERGE_APP_NAME=${concierge_app_name}
export PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS='${concierge_custom_labels}'
export PINNIPED_TEST_USER_USERNAME=${test_username} export PINNIPED_TEST_USER_USERNAME=${test_username}
export PINNIPED_TEST_USER_GROUPS=${test_groups} export PINNIPED_TEST_USER_GROUPS=${test_groups}
export PINNIPED_TEST_USER_TOKEN=${test_username}:${test_password} export PINNIPED_TEST_USER_TOKEN=${test_username}:${test_password}
@ -271,6 +276,7 @@ export PINNIPED_TEST_WEBHOOK_ENDPOINT=${webhook_url}
export PINNIPED_TEST_WEBHOOK_CA_BUNDLE=${webhook_ca_bundle} export PINNIPED_TEST_WEBHOOK_CA_BUNDLE=${webhook_ca_bundle}
export PINNIPED_TEST_SUPERVISOR_NAMESPACE=${supervisor_namespace} export PINNIPED_TEST_SUPERVISOR_NAMESPACE=${supervisor_namespace}
export PINNIPED_TEST_SUPERVISOR_APP_NAME=${supervisor_app_name} export PINNIPED_TEST_SUPERVISOR_APP_NAME=${supervisor_app_name}
export PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS='${supervisor_custom_labels}'
export PINNIPED_TEST_SUPERVISOR_ADDRESS="127.0.0.1:12345" export PINNIPED_TEST_SUPERVISOR_ADDRESS="127.0.0.1:12345"
export PINNIPED_TEST_CLI_OIDC_ISSUER=http://127.0.0.1:12346/dex export PINNIPED_TEST_CLI_OIDC_ISSUER=http://127.0.0.1:12346/dex
export PINNIPED_TEST_CLI_OIDC_CLIENT_ID=pinniped-cli export PINNIPED_TEST_CLI_OIDC_CLIENT_ID=pinniped-cli

View File

@ -17,13 +17,13 @@ import (
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/login/v1alpha1" loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/login/v1alpha1"
"go.pinniped.dev/internal/certauthority/dynamiccertauthority" "go.pinniped.dev/internal/certauthority/dynamiccertauthority"
"go.pinniped.dev/internal/concierge/apiserver" "go.pinniped.dev/internal/concierge/apiserver"
"go.pinniped.dev/internal/config/concierge"
"go.pinniped.dev/internal/controller/identityprovider/idpcache" "go.pinniped.dev/internal/controller/identityprovider/idpcache"
"go.pinniped.dev/internal/controllermanager" "go.pinniped.dev/internal/controllermanager"
"go.pinniped.dev/internal/downward" "go.pinniped.dev/internal/downward"
"go.pinniped.dev/internal/dynamiccert" "go.pinniped.dev/internal/dynamiccert"
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/registry/credentialrequest" "go.pinniped.dev/internal/registry/credentialrequest"
"go.pinniped.dev/pkg/config"
) )
// App is an object that represents the pinniped-concierge application. // App is an object that represents the pinniped-concierge application.
@ -92,7 +92,7 @@ func addCommandlineFlagsToCommand(cmd *cobra.Command, app *App) {
// Boot the aggregated API server, which will in turn boot the controllers. // Boot the aggregated API server, which will in turn boot the controllers.
func (a *App) runServer(ctx context.Context) error { func (a *App) runServer(ctx context.Context) error {
// Read the server config file. // Read the server config file.
cfg, err := config.FromPath(a.configPath) cfg, err := concierge.FromPath(a.configPath)
if err != nil { if err != nil {
return fmt.Errorf("could not load config: %w", err) return fmt.Errorf("could not load config: %w", err)
} }
@ -124,6 +124,7 @@ func (a *App) runServer(ctx context.Context) error {
&controllermanager.Config{ &controllermanager.Config{
ServerInstallationNamespace: serverInstallationNamespace, ServerInstallationNamespace: serverInstallationNamespace,
NamesConfig: &cfg.NamesConfig, NamesConfig: &cfg.NamesConfig,
Labels: cfg.Labels,
KubeCertAgentConfig: &cfg.KubeCertAgentConfig, KubeCertAgentConfig: &cfg.KubeCertAgentConfig,
DiscoveryURLOverride: cfg.DiscoveryInfo.URL, DiscoveryURLOverride: cfg.DiscoveryInfo.URL,
DynamicServingCertProvider: dynamicServingCertProvider, DynamicServingCertProvider: dynamicServingCertProvider,

View File

@ -1,9 +1,9 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Package config contains functionality to load/store api.Config's from/to // Package concierge contains functionality to load/store Config's from/to
// some source. // some source.
package config package concierge
import ( import (
"fmt" "fmt"
@ -13,7 +13,6 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/pkg/config/api"
) )
const ( const (
@ -21,20 +20,20 @@ const (
about9Months = 60 * 60 * 24 * 30 * 9 about9Months = 60 * 60 * 24 * 30 * 9
) )
// FromPath loads an api.Config from a provided local file path, inserts any // FromPath loads an Config from a provided local file path, inserts any
// defaults (from the api.Config documentation), and verifies that the config is // defaults (from the Config documentation), and verifies that the config is
// valid (per the api.Config documentation). // valid (per the Config documentation).
// //
// Note! The api.Config file should contain base64-encoded WebhookCABundle data. // Note! The Config file should contain base64-encoded WebhookCABundle data.
// This function will decode that base64-encoded data to PEM bytes to be stored // This function will decode that base64-encoded data to PEM bytes to be stored
// in the api.Config. // in the Config.
func FromPath(path string) (*api.Config, error) { func FromPath(path string) (*Config, error) {
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("read file: %w", err) return nil, fmt.Errorf("read file: %w", err)
} }
var config api.Config var config Config
if err := yaml.Unmarshal(data, &config); err != nil { if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("decode yaml: %w", err) return nil, fmt.Errorf("decode yaml: %w", err)
} }
@ -50,10 +49,14 @@ func FromPath(path string) (*api.Config, error) {
return nil, fmt.Errorf("validate names: %w", err) return nil, fmt.Errorf("validate names: %w", err)
} }
if config.Labels == nil {
config.Labels = make(map[string]string)
}
return &config, nil return &config, nil
} }
func maybeSetAPIDefaults(apiConfig *api.APIConfigSpec) { func maybeSetAPIDefaults(apiConfig *APIConfigSpec) {
if apiConfig.ServingCertificateConfig.DurationSeconds == nil { if apiConfig.ServingCertificateConfig.DurationSeconds == nil {
apiConfig.ServingCertificateConfig.DurationSeconds = int64Ptr(aboutAYear) apiConfig.ServingCertificateConfig.DurationSeconds = int64Ptr(aboutAYear)
} }
@ -63,7 +66,7 @@ func maybeSetAPIDefaults(apiConfig *api.APIConfigSpec) {
} }
} }
func maybeSetKubeCertAgentDefaults(cfg *api.KubeCertAgentSpec) { func maybeSetKubeCertAgentDefaults(cfg *KubeCertAgentSpec) {
if cfg.NamePrefix == nil { if cfg.NamePrefix == nil {
cfg.NamePrefix = stringPtr("pinniped-kube-cert-agent-") cfg.NamePrefix = stringPtr("pinniped-kube-cert-agent-")
} }
@ -73,7 +76,7 @@ func maybeSetKubeCertAgentDefaults(cfg *api.KubeCertAgentSpec) {
} }
} }
func validateNames(names *api.NamesConfigSpec) error { func validateNames(names *NamesConfigSpec) error {
missingNames := []string{} missingNames := []string{}
if names == nil { if names == nil {
missingNames = append(missingNames, "servingCertificateSecret", "credentialIssuerConfig", "apiService") missingNames = append(missingNames, "servingCertificateSecret", "credentialIssuerConfig", "apiService")
@ -94,7 +97,7 @@ func validateNames(names *api.NamesConfigSpec) error {
return nil return nil
} }
func validateAPI(apiConfig *api.APIConfigSpec) error { func validateAPI(apiConfig *APIConfigSpec) error {
if *apiConfig.ServingCertificateConfig.DurationSeconds < *apiConfig.ServingCertificateConfig.RenewBeforeSeconds { if *apiConfig.ServingCertificateConfig.DurationSeconds < *apiConfig.ServingCertificateConfig.RenewBeforeSeconds {
return constable.Error("durationSeconds cannot be smaller than renewBeforeSeconds") return constable.Error("durationSeconds cannot be smaller than renewBeforeSeconds")
} }

View File

@ -1,7 +1,7 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package config package concierge
import ( import (
"io/ioutil" "io/ioutil"
@ -11,14 +11,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/pkg/config/api"
) )
func TestFromPath(t *testing.T) { func TestFromPath(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
yaml string yaml string
wantConfig *api.Config wantConfig *Config
wantError string wantError string
}{ }{
{ {
@ -36,27 +35,34 @@ func TestFromPath(t *testing.T) {
credentialIssuerConfig: pinniped-config credentialIssuerConfig: pinniped-config
apiService: pinniped-api apiService: pinniped-api
kubeCertAgentPrefix: kube-cert-agent-prefix kubeCertAgentPrefix: kube-cert-agent-prefix
labels:
myLabelKey1: myLabelValue1
myLabelKey2: myLabelValue2
KubeCertAgent: KubeCertAgent:
namePrefix: kube-cert-agent-name-prefix- namePrefix: kube-cert-agent-name-prefix-
image: kube-cert-agent-image image: kube-cert-agent-image
imagePullSecrets: [kube-cert-agent-image-pull-secret] imagePullSecrets: [kube-cert-agent-image-pull-secret]
`), `),
wantConfig: &api.Config{ wantConfig: &Config{
DiscoveryInfo: api.DiscoveryInfoSpec{ DiscoveryInfo: DiscoveryInfoSpec{
URL: stringPtr("https://some.discovery/url"), URL: stringPtr("https://some.discovery/url"),
}, },
APIConfig: api.APIConfigSpec{ APIConfig: APIConfigSpec{
ServingCertificateConfig: api.ServingCertificateConfigSpec{ ServingCertificateConfig: ServingCertificateConfigSpec{
DurationSeconds: int64Ptr(3600), DurationSeconds: int64Ptr(3600),
RenewBeforeSeconds: int64Ptr(2400), RenewBeforeSeconds: int64Ptr(2400),
}, },
}, },
NamesConfig: api.NamesConfigSpec{ NamesConfig: NamesConfigSpec{
ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate", ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate",
CredentialIssuerConfig: "pinniped-config", CredentialIssuerConfig: "pinniped-config",
APIService: "pinniped-api", APIService: "pinniped-api",
}, },
KubeCertAgentConfig: api.KubeCertAgentSpec{ Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
KubeCertAgentConfig: KubeCertAgentSpec{
NamePrefix: stringPtr("kube-cert-agent-name-prefix-"), NamePrefix: stringPtr("kube-cert-agent-name-prefix-"),
Image: stringPtr("kube-cert-agent-image"), Image: stringPtr("kube-cert-agent-image"),
ImagePullSecrets: []string{"kube-cert-agent-image-pull-secret"}, ImagePullSecrets: []string{"kube-cert-agent-image-pull-secret"},
@ -72,22 +78,23 @@ func TestFromPath(t *testing.T) {
credentialIssuerConfig: pinniped-config credentialIssuerConfig: pinniped-config
apiService: pinniped-api apiService: pinniped-api
`), `),
wantConfig: &api.Config{ wantConfig: &Config{
DiscoveryInfo: api.DiscoveryInfoSpec{ DiscoveryInfo: DiscoveryInfoSpec{
URL: nil, URL: nil,
}, },
APIConfig: api.APIConfigSpec{ APIConfig: APIConfigSpec{
ServingCertificateConfig: api.ServingCertificateConfigSpec{ ServingCertificateConfig: ServingCertificateConfigSpec{
DurationSeconds: int64Ptr(60 * 60 * 24 * 365), // about a year DurationSeconds: int64Ptr(60 * 60 * 24 * 365), // about a year
RenewBeforeSeconds: int64Ptr(60 * 60 * 24 * 30 * 9), // about 9 months RenewBeforeSeconds: int64Ptr(60 * 60 * 24 * 30 * 9), // about 9 months
}, },
}, },
NamesConfig: api.NamesConfigSpec{ NamesConfig: NamesConfigSpec{
ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate", ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate",
CredentialIssuerConfig: "pinniped-config", CredentialIssuerConfig: "pinniped-config",
APIService: "pinniped-api", APIService: "pinniped-api",
}, },
KubeCertAgentConfig: api.KubeCertAgentSpec{ Labels: map[string]string{},
KubeCertAgentConfig: KubeCertAgentSpec{
NamePrefix: stringPtr("pinniped-kube-cert-agent-"), NamePrefix: stringPtr("pinniped-kube-cert-agent-"),
Image: stringPtr("debian:latest"), Image: stringPtr("debian:latest"),
}, },

View File

@ -1,19 +1,20 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package api package concierge
// Config contains knobs to setup an instance of Pinniped. // Config contains knobs to setup an instance of the Pinniped Concierge.
type Config struct { type Config struct {
DiscoveryInfo DiscoveryInfoSpec `json:"discovery"` DiscoveryInfo DiscoveryInfoSpec `json:"discovery"`
APIConfig APIConfigSpec `json:"api"` APIConfig APIConfigSpec `json:"api"`
NamesConfig NamesConfigSpec `json:"names"` NamesConfig NamesConfigSpec `json:"names"`
KubeCertAgentConfig KubeCertAgentSpec `json:"kubeCertAgent"` KubeCertAgentConfig KubeCertAgentSpec `json:"kubeCertAgent"`
Labels map[string]string `json:"labels"`
} }
// DiscoveryInfoSpec contains configuration knobs specific to // DiscoveryInfoSpec contains configuration knobs specific to
// pinniped's publishing of discovery information. These values can be // pinniped's publishing of discovery information. These values can be
// viewed as overrides, i.e., if these are set, then pinniped will // viewed as overrides, i.e., if these are set, then Pinniped will
// publish these values in its discovery document instead of the ones it finds. // publish these values in its discovery document instead of the ones it finds.
type DiscoveryInfoSpec struct { type DiscoveryInfoSpec struct {
// URL contains the URL at which pinniped can be contacted. // URL contains the URL at which pinniped can be contacted.
@ -26,7 +27,7 @@ type APIConfigSpec struct {
ServingCertificateConfig ServingCertificateConfigSpec `json:"servingCertificate"` ServingCertificateConfig ServingCertificateConfigSpec `json:"servingCertificate"`
} }
// NamesConfigSpec configures the names of some Kubernetes resources for Pinniped. // NamesConfigSpec configures the names of some Kubernetes resources for the Concierge.
type NamesConfigSpec struct { type NamesConfigSpec struct {
ServingCertificateSecret string `json:"servingCertificateSecret"` ServingCertificateSecret string `json:"servingCertificateSecret"`
CredentialIssuerConfig string `json:"credentialIssuerConfig"` CredentialIssuerConfig string `json:"credentialIssuerConfig"`

View File

@ -0,0 +1,34 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package supervisor contains functionality to load/store Config's from/to
// some source.
package supervisor
import (
"fmt"
"io/ioutil"
"sigs.k8s.io/yaml"
)
// FromPath loads an Config from a provided local file path, inserts any
// defaults (from the Config documentation), and verifies that the config is
// valid (Config documentation).
func FromPath(path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file: %w", err)
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("decode yaml: %w", err)
}
if config.Labels == nil {
config.Labels = make(map[string]string)
}
return &config, nil
}

View File

@ -0,0 +1,69 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package supervisor
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/here"
)
func TestFromPath(t *testing.T) {
tests := []struct {
name string
yaml string
wantConfig *Config
}{
{
name: "Happy",
yaml: here.Doc(`
---
labels:
myLabelKey1: myLabelValue1
myLabelKey2: myLabelValue2
`),
wantConfig: &Config{
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
},
},
{
name: "When only the required fields are present, causes other fields to be defaulted",
yaml: here.Doc(`
---
`),
wantConfig: &Config{
Labels: map[string]string{},
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
// Write yaml to temp file
f, err := ioutil.TempFile("", "pinniped-test-config-yaml-*")
require.NoError(t, err)
defer func() {
err := os.Remove(f.Name())
require.NoError(t, err)
}()
_, err = f.WriteString(test.yaml)
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
// Test FromPath()
config, err := FromPath(f.Name())
require.NoError(t, err)
require.Equal(t, test.wantConfig, config)
})
}
}

View File

@ -0,0 +1,9 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package supervisor
// Config contains knobs to setup an instance of the Pinniped Supervisor.
type Config struct {
Labels map[string]string `json:"labels"`
}

View File

@ -29,6 +29,7 @@ const (
type certsManagerController struct { type certsManagerController struct {
namespace string namespace string
certsSecretResourceName string certsSecretResourceName string
certsSecretLabels map[string]string
k8sClient kubernetes.Interface k8sClient kubernetes.Interface
secretInformer corev1informers.SecretInformer secretInformer corev1informers.SecretInformer
@ -43,6 +44,7 @@ type certsManagerController struct {
func NewCertsManagerController( func NewCertsManagerController(
namespace string, namespace string,
certsSecretResourceName string, certsSecretResourceName string,
certsSecretLabels map[string]string,
k8sClient kubernetes.Interface, k8sClient kubernetes.Interface,
secretInformer corev1informers.SecretInformer, secretInformer corev1informers.SecretInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc, withInformer pinnipedcontroller.WithInformerOptionFunc,
@ -57,6 +59,7 @@ func NewCertsManagerController(
Syncer: &certsManagerController{ Syncer: &certsManagerController{
namespace: namespace, namespace: namespace,
certsSecretResourceName: certsSecretResourceName, certsSecretResourceName: certsSecretResourceName,
certsSecretLabels: certsSecretLabels,
k8sClient: k8sClient, k8sClient: k8sClient,
secretInformer: secretInformer, secretInformer: secretInformer,
certDuration: certDuration, certDuration: certDuration,
@ -116,6 +119,7 @@ func (c *certsManagerController) Sync(ctx controllerlib.Context) error {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: c.certsSecretResourceName, Name: c.certsSecretResourceName,
Namespace: c.namespace, Namespace: c.namespace,
Labels: c.certsSecretLabels,
}, },
StringData: map[string]string{ StringData: map[string]string{
caCertificateSecretKey: string(aggregatedAPIServerCA.Bundle()), caCertificateSecretKey: string(aggregatedAPIServerCA.Bundle()),

View File

@ -42,6 +42,7 @@ func TestManagerControllerOptions(t *testing.T) {
_ = NewCertsManagerController( _ = NewCertsManagerController(
installedInNamespace, installedInNamespace,
certsSecretResourceName, certsSecretResourceName,
make(map[string]string),
nil, nil,
secretsInformer, secretsInformer,
observableWithInformerOption.WithInformer, observableWithInformerOption.WithInformer,
@ -135,6 +136,10 @@ func TestManagerControllerSync(t *testing.T) {
subject = NewCertsManagerController( subject = NewCertsManagerController(
installedInNamespace, installedInNamespace,
certsSecretResourceName, certsSecretResourceName,
map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
kubeAPIClient, kubeAPIClient,
kubeInformers.Core().V1().Secrets(), kubeInformers.Core().V1().Secrets(),
controllerlib.WithInformer, controllerlib.WithInformer,
@ -198,6 +203,10 @@ func TestManagerControllerSync(t *testing.T) {
actualSecret := actualAction.GetObject().(*corev1.Secret) actualSecret := actualAction.GetObject().(*corev1.Secret)
r.Equal(certsSecretResourceName, actualSecret.Name) r.Equal(certsSecretResourceName, actualSecret.Name)
r.Equal(installedInNamespace, actualSecret.Namespace) r.Equal(installedInNamespace, actualSecret.Namespace)
r.Equal(map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
}, actualSecret.Labels)
actualCACert := actualSecret.StringData["caCertificate"] actualCACert := actualSecret.StringData["caCertificate"]
actualPrivateKey := actualSecret.StringData["tlsPrivateKey"] actualPrivateKey := actualSecret.StringData["tlsPrivateKey"]
actualCertChain := actualSecret.StringData["tlsCertificateChain"] actualCertChain := actualSecret.StringData["tlsCertificateChain"]

View File

@ -21,6 +21,7 @@ func CreateOrUpdateCredentialIssuerConfig(
ctx context.Context, ctx context.Context,
credentialIssuerConfigNamespace string, credentialIssuerConfigNamespace string,
credentialIssuerConfigResourceName string, credentialIssuerConfigResourceName string,
credentialIssuerConfigLabels map[string]string,
pinnipedClient pinnipedclientset.Interface, pinnipedClient pinnipedclientset.Interface,
applyUpdatesToCredentialIssuerConfigFunc func(configToUpdate *configv1alpha1.CredentialIssuerConfig), applyUpdatesToCredentialIssuerConfigFunc func(configToUpdate *configv1alpha1.CredentialIssuerConfig),
) error { ) error {
@ -39,7 +40,9 @@ func CreateOrUpdateCredentialIssuerConfig(
if notFound { if notFound {
// Create it // Create it
credentialIssuerConfig := minimalValidCredentialIssuerConfig(credentialIssuerConfigResourceName, credentialIssuerConfigNamespace) credentialIssuerConfig := minimalValidCredentialIssuerConfig(
credentialIssuerConfigResourceName, credentialIssuerConfigNamespace, credentialIssuerConfigLabels,
)
applyUpdatesToCredentialIssuerConfigFunc(credentialIssuerConfig) applyUpdatesToCredentialIssuerConfigFunc(credentialIssuerConfig)
if _, err := credentialIssuerConfigsClient.Create(ctx, credentialIssuerConfig, metav1.CreateOptions{}); err != nil { if _, err := credentialIssuerConfigsClient.Create(ctx, credentialIssuerConfig, metav1.CreateOptions{}); err != nil {
@ -71,12 +74,14 @@ func CreateOrUpdateCredentialIssuerConfig(
func minimalValidCredentialIssuerConfig( func minimalValidCredentialIssuerConfig(
credentialIssuerConfigName string, credentialIssuerConfigName string,
credentialIssuerConfigNamespace string, credentialIssuerConfigNamespace string,
credentialIssuerConfigLabels map[string]string,
) *configv1alpha1.CredentialIssuerConfig { ) *configv1alpha1.CredentialIssuerConfig {
return &configv1alpha1.CredentialIssuerConfig{ return &configv1alpha1.CredentialIssuerConfig{
TypeMeta: metav1.TypeMeta{}, TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: credentialIssuerConfigName, Name: credentialIssuerConfigName,
Namespace: credentialIssuerConfigNamespace, Namespace: credentialIssuerConfigNamespace,
Labels: credentialIssuerConfigLabels,
}, },
Status: configv1alpha1.CredentialIssuerConfigStatus{ Status: configv1alpha1.CredentialIssuerConfigStatus{
Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{}, Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{},

View File

@ -45,7 +45,15 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
when("the config does not exist", func() { when("the config does not exist", func() {
it("creates a new config which includes only the updates made by the func parameter", func() { it("creates a new config which includes only the updates made by the func parameter", func() {
err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, err := CreateOrUpdateCredentialIssuerConfig(
ctx,
installationNamespace,
credentialIssuerConfigResourceName,
map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) { func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
configToUpdate.Status.KubeConfigInfo = &configv1alpha1.CredentialIssuerConfigKubeConfigInfo{ configToUpdate.Status.KubeConfigInfo = &configv1alpha1.CredentialIssuerConfigKubeConfigInfo{
CertificateAuthorityData: "some-ca-value", CertificateAuthorityData: "some-ca-value",
@ -64,6 +72,10 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: credentialIssuerConfigResourceName, Name: credentialIssuerConfigResourceName,
Namespace: installationNamespace, Namespace: installationNamespace,
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
}, },
Status: configv1alpha1.CredentialIssuerConfigStatus{ Status: configv1alpha1.CredentialIssuerConfigStatus{
Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{}, Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{},
@ -86,7 +98,12 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
}) })
it("returns an error", func() { it("returns an error", func() {
err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, err := CreateOrUpdateCredentialIssuerConfig(
ctx,
installationNamespace,
credentialIssuerConfigResourceName,
map[string]string{},
pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {}, func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {},
) )
r.EqualError(err, "could not create or update credentialissuerconfig: create failed: error on create") r.EqualError(err, "could not create or update credentialissuerconfig: create failed: error on create")
@ -103,6 +120,9 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: credentialIssuerConfigResourceName, Name: credentialIssuerConfigResourceName,
Namespace: installationNamespace, Namespace: installationNamespace,
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
},
}, },
Status: configv1alpha1.CredentialIssuerConfigStatus{ Status: configv1alpha1.CredentialIssuerConfigStatus{
Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{ Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{
@ -124,7 +144,15 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
}) })
it("updates the existing config to only apply the updates made by the func parameter", func() { it("updates the existing config to only apply the updates made by the func parameter", func() {
err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, err := CreateOrUpdateCredentialIssuerConfig(
ctx,
installationNamespace,
credentialIssuerConfigResourceName,
map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) { func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value"
}, },
@ -142,7 +170,12 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
}) })
it("avoids the cost of an update if the local updates made by the func parameter did not actually change anything", func() { it("avoids the cost of an update if the local updates made by the func parameter did not actually change anything", func() {
err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, err := CreateOrUpdateCredentialIssuerConfig(
ctx,
installationNamespace,
credentialIssuerConfigResourceName,
map[string]string{},
pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) { func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "initial-ca-value" configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "initial-ca-value"
@ -166,7 +199,12 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
}) })
it("returns an error", func() { it("returns an error", func() {
err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, err := CreateOrUpdateCredentialIssuerConfig(
ctx,
installationNamespace,
credentialIssuerConfigResourceName,
map[string]string{},
pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {}, func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {},
) )
r.EqualError(err, "could not create or update credentialissuerconfig: get failed: error on get") r.EqualError(err, "could not create or update credentialissuerconfig: get failed: error on get")
@ -181,7 +219,12 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
}) })
it("returns an error", func() { it("returns an error", func() {
err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, err := CreateOrUpdateCredentialIssuerConfig(
ctx,
installationNamespace,
credentialIssuerConfigResourceName,
map[string]string{},
pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) { func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value"
}, },
@ -215,7 +258,15 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) {
}) })
it("retries updates on conflict", func() { it("retries updates on conflict", func() {
err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, err := CreateOrUpdateCredentialIssuerConfig(
ctx,
installationNamespace,
credentialIssuerConfigResourceName,
map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) { func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value"
}, },

View File

@ -27,6 +27,7 @@ const (
type kubeConigInfoPublisherController struct { type kubeConigInfoPublisherController struct {
credentialIssuerConfigNamespaceName string credentialIssuerConfigNamespaceName string
credentialIssuerConfigResourceName string credentialIssuerConfigResourceName string
credentialIssuerConfigLabels map[string]string
serverOverride *string serverOverride *string
pinnipedClient pinnipedclientset.Interface pinnipedClient pinnipedclientset.Interface
configMapInformer corev1informers.ConfigMapInformer configMapInformer corev1informers.ConfigMapInformer
@ -38,6 +39,7 @@ type kubeConigInfoPublisherController struct {
func NewKubeConfigInfoPublisherController( func NewKubeConfigInfoPublisherController(
credentialIssuerConfigNamespaceName string, credentialIssuerConfigNamespaceName string,
credentialIssuerConfigResourceName string, credentialIssuerConfigResourceName string,
credentialIssuerConfigLabels map[string]string,
serverOverride *string, serverOverride *string,
pinnipedClient pinnipedclientset.Interface, pinnipedClient pinnipedclientset.Interface,
configMapInformer corev1informers.ConfigMapInformer, configMapInformer corev1informers.ConfigMapInformer,
@ -49,6 +51,7 @@ func NewKubeConfigInfoPublisherController(
Syncer: &kubeConigInfoPublisherController{ Syncer: &kubeConigInfoPublisherController{
credentialIssuerConfigResourceName: credentialIssuerConfigResourceName, credentialIssuerConfigResourceName: credentialIssuerConfigResourceName,
credentialIssuerConfigNamespaceName: credentialIssuerConfigNamespaceName, credentialIssuerConfigNamespaceName: credentialIssuerConfigNamespaceName,
credentialIssuerConfigLabels: credentialIssuerConfigLabels,
serverOverride: serverOverride, serverOverride: serverOverride,
pinnipedClient: pinnipedClient, pinnipedClient: pinnipedClient,
configMapInformer: configMapInformer, configMapInformer: configMapInformer,
@ -114,6 +117,7 @@ func (c *kubeConigInfoPublisherController) Sync(ctx controllerlib.Context) error
ctx.Context, ctx.Context,
c.credentialIssuerConfigNamespaceName, c.credentialIssuerConfigNamespaceName,
c.credentialIssuerConfigResourceName, c.credentialIssuerConfigResourceName,
c.credentialIssuerConfigLabels,
c.pinnipedClient, c.pinnipedClient,
updateServerAndCAFunc, updateServerAndCAFunc,
) )

View File

@ -43,6 +43,7 @@ func TestInformerFilters(t *testing.T) {
_ = NewKubeConfigInfoPublisherController( _ = NewKubeConfigInfoPublisherController(
installedInNamespace, installedInNamespace,
credentialIssuerConfigResourceName, credentialIssuerConfigResourceName,
map[string]string{},
nil, nil,
nil, nil,
configMapInformer, configMapInformer,
@ -127,6 +128,10 @@ func TestSync(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: credentialIssuerConfigResourceName, Name: credentialIssuerConfigResourceName,
Namespace: expectedNamespace, Namespace: expectedNamespace,
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
}, },
Status: configv1alpha1.CredentialIssuerConfigStatus{ Status: configv1alpha1.CredentialIssuerConfigStatus{
Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{}, Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{},
@ -146,6 +151,10 @@ func TestSync(t *testing.T) {
subject = NewKubeConfigInfoPublisherController( subject = NewKubeConfigInfoPublisherController(
installedInNamespace, installedInNamespace,
credentialIssuerConfigResourceName, credentialIssuerConfigResourceName,
map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
serverOverride, serverOverride,
pinnipedAPIClient, pinnipedAPIClient,
kubeInformers.Core().V1().ConfigMaps(), kubeInformers.Core().V1().ConfigMaps(),

View File

@ -122,13 +122,7 @@ func (c *annotaterController) Sync(ctx controllerlib.Context) error {
keyPath, keyPath,
); err != nil { ); err != nil {
err = fmt.Errorf("cannot update agent pod: %w", err) err = fmt.Errorf("cannot update agent pod: %w", err)
strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig( strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig(ctx.Context, *c.credentialIssuerConfigLocationConfig, nil, c.clock, c.pinnipedAPIClient, err)
ctx.Context,
*c.credentialIssuerConfigLocationConfig,
c.clock,
c.pinnipedAPIClient,
err,
)
if strategyResultUpdateErr != nil { if strategyResultUpdateErr != nil {
// If the CIC update fails, then we probably want to try again. This controller will get // If the CIC update fails, then we probably want to try again. This controller will get
// called again because of the pod create failure, so just try the CIC update again then. // called again because of the pod create failure, so just try the CIC update again then.

View File

@ -23,6 +23,7 @@ import (
type createrController struct { type createrController struct {
agentPodConfig *AgentPodConfig agentPodConfig *AgentPodConfig
credentialIssuerConfigLocationConfig *CredentialIssuerConfigLocationConfig credentialIssuerConfigLocationConfig *CredentialIssuerConfigLocationConfig
credentialIssuerConfigLabels map[string]string
clock clock.Clock clock clock.Clock
k8sClient kubernetes.Interface k8sClient kubernetes.Interface
pinnipedAPIClient pinnipedclientset.Interface pinnipedAPIClient pinnipedclientset.Interface
@ -38,6 +39,7 @@ type createrController struct {
func NewCreaterController( func NewCreaterController(
agentPodConfig *AgentPodConfig, agentPodConfig *AgentPodConfig,
credentialIssuerConfigLocationConfig *CredentialIssuerConfigLocationConfig, credentialIssuerConfigLocationConfig *CredentialIssuerConfigLocationConfig,
credentialIssuerConfigLabels map[string]string,
clock clock.Clock, clock clock.Clock,
k8sClient kubernetes.Interface, k8sClient kubernetes.Interface,
pinnipedAPIClient pinnipedclientset.Interface, pinnipedAPIClient pinnipedclientset.Interface,
@ -53,6 +55,7 @@ func NewCreaterController(
Syncer: &createrController{ Syncer: &createrController{
agentPodConfig: agentPodConfig, agentPodConfig: agentPodConfig,
credentialIssuerConfigLocationConfig: credentialIssuerConfigLocationConfig, credentialIssuerConfigLocationConfig: credentialIssuerConfigLocationConfig,
credentialIssuerConfigLabels: credentialIssuerConfigLabels,
clock: clock, clock: clock,
k8sClient: k8sClient, k8sClient: k8sClient,
pinnipedAPIClient: pinnipedAPIClient, pinnipedAPIClient: pinnipedAPIClient,
@ -95,6 +98,7 @@ func (c *createrController) Sync(ctx controllerlib.Context) error {
return createOrUpdateCredentialIssuerConfig( return createOrUpdateCredentialIssuerConfig(
ctx.Context, ctx.Context,
*c.credentialIssuerConfigLocationConfig, *c.credentialIssuerConfigLocationConfig,
c.credentialIssuerConfigLabels,
c.clock, c.clock,
c.pinnipedAPIClient, c.pinnipedAPIClient,
constable.Error("did not find kube-controller-manager pod(s)"), constable.Error("did not find kube-controller-manager pod(s)"),
@ -129,6 +133,7 @@ func (c *createrController) Sync(ctx controllerlib.Context) error {
strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig( strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig(
ctx.Context, ctx.Context,
*c.credentialIssuerConfigLocationConfig, *c.credentialIssuerConfigLocationConfig,
c.credentialIssuerConfigLabels,
c.clock, c.clock,
c.pinnipedAPIClient, c.pinnipedAPIClient,
err, err,

View File

@ -42,7 +42,8 @@ func TestCreaterControllerFilter(t *testing.T) {
_ = NewCreaterController( _ = NewCreaterController(
agentPodConfig, agentPodConfig,
credentialIssuerConfigLocationConfig, credentialIssuerConfigLocationConfig,
nil, // clock, shound't matter map[string]string{},
nil, // clock, shouldn't matter
nil, // k8sClient, shouldn't matter nil, // k8sClient, shouldn't matter
nil, // pinnipedAPIClient, shouldn't matter nil, // pinnipedAPIClient, shouldn't matter
kubeSystemPodInformer, kubeSystemPodInformer,
@ -66,7 +67,8 @@ func TestCreaterControllerInitialEvent(t *testing.T) {
_ = NewCreaterController( _ = NewCreaterController(
nil, // agentPodConfig, shouldn't matter nil, // agentPodConfig, shouldn't matter
nil, // credentialIssuerConfigLocationConfig, shouldn't matter nil, // credentialIssuerConfigLocationConfig, shouldn't matter
nil, // clock, shound't matter map[string]string{},
nil, // clock, shouldn't matter
nil, // k8sClient, shouldn't matter nil, // k8sClient, shouldn't matter
nil, // pinnipedAPIClient, shouldn't matter nil, // pinnipedAPIClient, shouldn't matter
kubeSystemInformers.Core().V1().Pods(), kubeSystemInformers.Core().V1().Pods(),
@ -111,11 +113,19 @@ func TestCreaterControllerSync(t *testing.T) {
ContainerImage: "some-agent-image", ContainerImage: "some-agent-image",
PodNamePrefix: "some-agent-name-", PodNamePrefix: "some-agent-name-",
ContainerImagePullSecrets: []string{"some-image-pull-secret"}, ContainerImagePullSecrets: []string{"some-image-pull-secret"},
AdditionalLabels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
}, },
&CredentialIssuerConfigLocationConfig{ &CredentialIssuerConfigLocationConfig{
Namespace: credentialIssuerConfigNamespaceName, Namespace: credentialIssuerConfigNamespaceName,
Name: credentialIssuerConfigResourceName, Name: credentialIssuerConfigResourceName,
}, },
map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
clock.NewFakeClock(frozenNow), clock.NewFakeClock(frozenNow),
kubeAPIClient, kubeAPIClient,
pinnipedAPIClient, pinnipedAPIClient,
@ -361,6 +371,10 @@ func TestCreaterControllerSync(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: credentialIssuerConfigResourceName, Name: credentialIssuerConfigResourceName,
Namespace: credentialIssuerConfigNamespaceName, Namespace: credentialIssuerConfigNamespaceName,
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
}, },
Status: configv1alpha1.CredentialIssuerConfigStatus{ Status: configv1alpha1.CredentialIssuerConfigStatus{
Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{ Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{
@ -502,6 +516,10 @@ func TestCreaterControllerSync(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: credentialIssuerConfigResourceName, Name: credentialIssuerConfigResourceName,
Namespace: credentialIssuerConfigNamespaceName, Namespace: credentialIssuerConfigNamespaceName,
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
}, },
Status: configv1alpha1.CredentialIssuerConfigStatus{ Status: configv1alpha1.CredentialIssuerConfigStatus{
Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{ Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{

View File

@ -87,39 +87,21 @@ func (c *execerController) Sync(ctx controllerlib.Context) error {
certPEM, err := c.podCommandExecutor.Exec(agentPod.Namespace, agentPod.Name, "cat", certPath) certPEM, err := c.podCommandExecutor.Exec(agentPod.Namespace, agentPod.Name, "cat", certPath)
if err != nil { if err != nil {
strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig( strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig(ctx.Context, *c.credentialIssuerConfigLocationConfig, nil, c.clock, c.pinnipedAPIClient, err)
ctx.Context,
*c.credentialIssuerConfigLocationConfig,
c.clock,
c.pinnipedAPIClient,
err,
)
klog.ErrorS(strategyResultUpdateErr, "could not create or update CredentialIssuerConfig with strategy success") klog.ErrorS(strategyResultUpdateErr, "could not create or update CredentialIssuerConfig with strategy success")
return err return err
} }
keyPEM, err := c.podCommandExecutor.Exec(agentPod.Namespace, agentPod.Name, "cat", keyPath) keyPEM, err := c.podCommandExecutor.Exec(agentPod.Namespace, agentPod.Name, "cat", keyPath)
if err != nil { if err != nil {
strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig( strategyResultUpdateErr := createOrUpdateCredentialIssuerConfig(ctx.Context, *c.credentialIssuerConfigLocationConfig, nil, c.clock, c.pinnipedAPIClient, err)
ctx.Context,
*c.credentialIssuerConfigLocationConfig,
c.clock,
c.pinnipedAPIClient,
err,
)
klog.ErrorS(strategyResultUpdateErr, "could not create or update CredentialIssuerConfig with strategy success") klog.ErrorS(strategyResultUpdateErr, "could not create or update CredentialIssuerConfig with strategy success")
return err return err
} }
c.dynamicCertProvider.Set([]byte(certPEM), []byte(keyPEM)) c.dynamicCertProvider.Set([]byte(certPEM), []byte(keyPEM))
err = createOrUpdateCredentialIssuerConfig( err = createOrUpdateCredentialIssuerConfig(ctx.Context, *c.credentialIssuerConfigLocationConfig, nil, c.clock, c.pinnipedAPIClient, nil)
ctx.Context,
*c.credentialIssuerConfigLocationConfig,
c.clock,
c.pinnipedAPIClient,
nil, // nil error = success! yay!
)
if err != nil { if err != nil {
return err return err
} }

View File

@ -67,6 +67,9 @@ type AgentPodConfig struct {
// ContainerImagePullSecrets is a list of names of Kubernetes Secret objects that will be used as // ContainerImagePullSecrets is a list of names of Kubernetes Secret objects that will be used as
// ImagePullSecrets on the kube-cert-agent pods. // ImagePullSecrets on the kube-cert-agent pods.
ContainerImagePullSecrets []string ContainerImagePullSecrets []string
// Additional labels that should be added to every agent pod during creation.
AdditionalLabels map[string]string
} }
type CredentialIssuerConfigLocationConfig struct { type CredentialIssuerConfigLocationConfig struct {
@ -78,9 +81,13 @@ type CredentialIssuerConfigLocationConfig struct {
} }
func (c *AgentPodConfig) Labels() map[string]string { func (c *AgentPodConfig) Labels() map[string]string {
return map[string]string{ labels := map[string]string{
agentPodLabelKey: agentPodLabelValue, agentPodLabelKey: agentPodLabelValue,
} }
for k, v := range c.AdditionalLabels {
labels[k] = v
}
return labels
} }
func (c *AgentPodConfig) PodTemplate() *corev1.Pod { func (c *AgentPodConfig) PodTemplate() *corev1.Pod {
@ -258,9 +265,9 @@ func findControllerManagerPodForSpecificAgentPod(
return maybeControllerManagerPod, nil return maybeControllerManagerPod, nil
} }
func createOrUpdateCredentialIssuerConfig( func createOrUpdateCredentialIssuerConfig(ctx context.Context,
ctx context.Context,
cicConfig CredentialIssuerConfigLocationConfig, cicConfig CredentialIssuerConfigLocationConfig,
credentialIssuerConfigLabels map[string]string,
clock clock.Clock, clock clock.Clock,
pinnipedAPIClient pinnipedclientset.Interface, pinnipedAPIClient pinnipedclientset.Interface,
err error, err error,
@ -269,6 +276,7 @@ func createOrUpdateCredentialIssuerConfig(
ctx, ctx,
cicConfig.Namespace, cicConfig.Namespace,
cicConfig.Name, cicConfig.Name,
credentialIssuerConfigLabels,
pinnipedAPIClient, pinnipedAPIClient,
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) { func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
var strategyResult configv1alpha1.CredentialIssuerConfigStrategy var strategyResult configv1alpha1.CredentialIssuerConfigStrategy

View File

@ -79,6 +79,8 @@ func exampleControllerManagerAndAgentPods(
Namespace: agentPodNamespace, Namespace: agentPodNamespace,
Labels: map[string]string{ Labels: map[string]string{
"kube-cert-agent.pinniped.dev": "true", "kube-cert-agent.pinniped.dev": "true",
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
}, },
Annotations: map[string]string{ Annotations: map[string]string{
"kube-cert-agent.pinniped.dev/controller-manager-name": controllerManagerPod.Name, "kube-cert-agent.pinniped.dev/controller-manager-name": controllerManagerPod.Name,

View File

@ -56,6 +56,7 @@ func generateECKey(r io.Reader) (interface{}, error) {
// jwkController holds the fields necessary for the JWKS controller to communicate with OPC's and // jwkController holds the fields necessary for the JWKS controller to communicate with OPC's and
// secrets, both via a cache and via the API. // secrets, both via a cache and via the API.
type jwksController struct { type jwksController struct {
jwksSecretLabels map[string]string
pinnipedClient pinnipedclientset.Interface pinnipedClient pinnipedclientset.Interface
kubeClient kubernetes.Interface kubeClient kubernetes.Interface
opcInformer configinformers.OIDCProviderConfigInformer opcInformer configinformers.OIDCProviderConfigInformer
@ -65,6 +66,7 @@ type jwksController struct {
// NewJWKSController returns a controllerlib.Controller that ensures an OPC has a corresponding // NewJWKSController returns a controllerlib.Controller that ensures an OPC has a corresponding
// Secret that contains a valid active JWK and JWKS. // Secret that contains a valid active JWK and JWKS.
func NewJWKSController( func NewJWKSController(
jwksSecretLabels map[string]string,
kubeClient kubernetes.Interface, kubeClient kubernetes.Interface,
pinnipedClient pinnipedclientset.Interface, pinnipedClient pinnipedclientset.Interface,
secretInformer corev1informers.SecretInformer, secretInformer corev1informers.SecretInformer,
@ -75,6 +77,7 @@ func NewJWKSController(
controllerlib.Config{ controllerlib.Config{
Name: "JWKSController", Name: "JWKSController",
Syncer: &jwksController{ Syncer: &jwksController{
jwksSecretLabels: jwksSecretLabels,
kubeClient: kubeClient, kubeClient: kubeClient,
pinnipedClient: pinnipedClient, pinnipedClient: pinnipedClient,
secretInformer: secretInformer, secretInformer: secretInformer,
@ -234,6 +237,7 @@ func (c *jwksController) generateSecret(opc *configv1alpha1.OIDCProviderConfig)
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: opc.Name + "-jwks", Name: opc.Name + "-jwks",
Namespace: opc.Namespace, Namespace: opc.Namespace,
Labels: c.jwksSecretLabels,
OwnerReferences: []metav1.OwnerReference{ OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(opc, schema.GroupVersionKind{ *metav1.NewControllerRef(opc, schema.GroupVersionKind{
Group: configv1alpha1.SchemeGroupVersion.Group, Group: configv1alpha1.SchemeGroupVersion.Group,
@ -241,7 +245,6 @@ func (c *jwksController) generateSecret(opc *configv1alpha1.OIDCProviderConfig)
Kind: opcKind, Kind: opcKind,
}), }),
}, },
// TODO: custom labels.
}, },
Data: map[string][]byte{ Data: map[string][]byte{
activeJWKKey: jwkData, activeJWKKey: jwkData,

View File

@ -151,6 +151,7 @@ func TestJWKSControllerFilterSecret(t *testing.T) {
).Config().V1alpha1().OIDCProviderConfigs() ).Config().V1alpha1().OIDCProviderConfigs()
withInformer := testutil.NewObservableWithInformerOption() withInformer := testutil.NewObservableWithInformerOption()
_ = NewJWKSController( _ = NewJWKSController(
nil, // labels, not needed
nil, // kubeClient, not needed nil, // kubeClient, not needed
nil, // pinnipedClient, not needed nil, // pinnipedClient, not needed
secretInformer, secretInformer,
@ -204,6 +205,7 @@ func TestJWKSControllerFilterOPC(t *testing.T) {
).Config().V1alpha1().OIDCProviderConfigs() ).Config().V1alpha1().OIDCProviderConfigs()
withInformer := testutil.NewObservableWithInformerOption() withInformer := testutil.NewObservableWithInformerOption()
_ = NewJWKSController( _ = NewJWKSController(
nil, // labels, not needed
nil, // kubeClient, not needed nil, // kubeClient, not needed
nil, // pinnipedClient, not needed nil, // pinnipedClient, not needed
secretInformer, secretInformer,
@ -264,6 +266,10 @@ func TestJWKSControllerSync(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: goodOPCWithStatus.Status.JWKSSecret.Name, Name: goodOPCWithStatus.Status.JWKSSecret.Name,
Namespace: namespace, Namespace: namespace,
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
OwnerReferences: []metav1.OwnerReference{ OwnerReferences: []metav1.OwnerReference{
{ {
APIVersion: opcGVR.GroupVersion().String(), APIVersion: opcGVR.GroupVersion().String(),
@ -648,6 +654,10 @@ func TestJWKSControllerSync(t *testing.T) {
) )
c := NewJWKSController( c := NewJWKSController(
map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
kubeAPIClient, kubeAPIClient,
pinnipedAPIClient, pinnipedAPIClient,
kubeInformers.Core().V1().Secrets(), kubeInformers.Core().V1().Secrets(),

View File

@ -22,6 +22,7 @@ import (
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/login/v1alpha1" loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/login/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned" pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
pinnipedinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions" pinnipedinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions"
"go.pinniped.dev/internal/config/concierge"
"go.pinniped.dev/internal/controller/apicerts" "go.pinniped.dev/internal/controller/apicerts"
"go.pinniped.dev/internal/controller/identityprovider/idpcache" "go.pinniped.dev/internal/controller/identityprovider/idpcache"
"go.pinniped.dev/internal/controller/identityprovider/webhookcachecleaner" "go.pinniped.dev/internal/controller/identityprovider/webhookcachecleaner"
@ -30,7 +31,6 @@ import (
"go.pinniped.dev/internal/controller/kubecertagent" "go.pinniped.dev/internal/controller/kubecertagent"
"go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/dynamiccert" "go.pinniped.dev/internal/dynamiccert"
"go.pinniped.dev/pkg/config/api"
) )
const ( const (
@ -47,11 +47,11 @@ type Config struct {
// NamesConfig comes from the Pinniped config API (see api.Config). It specifies how Kubernetes // NamesConfig comes from the Pinniped config API (see api.Config). It specifies how Kubernetes
// objects should be named. // objects should be named.
NamesConfig *api.NamesConfigSpec NamesConfig *concierge.NamesConfigSpec
// KubeCertAgentConfig comes from the Pinniped config API (see api.Config). It configures how // KubeCertAgentConfig comes from the Pinniped config API (see api.Config). It configures how
// the kubecertagent package's controllers should manage the agent pods. // the kubecertagent package's controllers should manage the agent pods.
KubeCertAgentConfig *api.KubeCertAgentSpec KubeCertAgentConfig *concierge.KubeCertAgentSpec
// DiscoveryURLOverride allows a caller to inject a hardcoded discovery URL into Pinniped // DiscoveryURLOverride allows a caller to inject a hardcoded discovery URL into Pinniped
// discovery document. // discovery document.
@ -72,6 +72,9 @@ type Config struct {
// IDPCache is a cache of authenticators shared amongst various IDP-related controllers. // IDPCache is a cache of authenticators shared amongst various IDP-related controllers.
IDPCache *idpcache.Cache IDPCache *idpcache.Cache
// Labels are labels that should be added to any resources created by the controllers.
Labels map[string]string
} }
// Prepare the controllers and their informers and return a function that will start them when called. // Prepare the controllers and their informers and return a function that will start them when called.
@ -96,6 +99,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
ContainerImage: *c.KubeCertAgentConfig.Image, ContainerImage: *c.KubeCertAgentConfig.Image,
PodNamePrefix: *c.KubeCertAgentConfig.NamePrefix, PodNamePrefix: *c.KubeCertAgentConfig.NamePrefix,
ContainerImagePullSecrets: c.KubeCertAgentConfig.ImagePullSecrets, ContainerImagePullSecrets: c.KubeCertAgentConfig.ImagePullSecrets,
AdditionalLabels: c.Labels,
} }
credentialIssuerConfigLocationConfig := &kubecertagent.CredentialIssuerConfigLocationConfig{ credentialIssuerConfigLocationConfig := &kubecertagent.CredentialIssuerConfigLocationConfig{
Namespace: c.ServerInstallationNamespace, Namespace: c.ServerInstallationNamespace,
@ -112,6 +116,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
issuerconfig.NewKubeConfigInfoPublisherController( issuerconfig.NewKubeConfigInfoPublisherController(
c.ServerInstallationNamespace, c.ServerInstallationNamespace,
c.NamesConfig.CredentialIssuerConfig, c.NamesConfig.CredentialIssuerConfig,
c.Labels,
c.DiscoveryURLOverride, c.DiscoveryURLOverride,
pinnipedClient, pinnipedClient,
informers.kubePublicNamespaceK8s.Core().V1().ConfigMaps(), informers.kubePublicNamespaceK8s.Core().V1().ConfigMaps(),
@ -125,6 +130,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
apicerts.NewCertsManagerController( apicerts.NewCertsManagerController(
c.ServerInstallationNamespace, c.ServerInstallationNamespace,
c.NamesConfig.ServingCertificateSecret, c.NamesConfig.ServingCertificateSecret,
c.Labels,
k8sClient, k8sClient,
informers.installationNamespaceK8s.Core().V1().Secrets(), informers.installationNamespaceK8s.Core().V1().Secrets(),
controllerlib.WithInformer, controllerlib.WithInformer,
@ -174,6 +180,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
kubecertagent.NewCreaterController( kubecertagent.NewCreaterController(
agentPodConfig, agentPodConfig,
credentialIssuerConfigLocationConfig, credentialIssuerConfigLocationConfig,
c.Labels,
clock.RealClock{}, clock.RealClock{},
k8sClient, k8sClient,
pinnipedClient, pinnipedClient,

View File

@ -90,6 +90,10 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) {
require.NotEmpty(t, initialCACert) require.NotEmpty(t, initialCACert)
require.NotEmpty(t, initialPrivateKey) require.NotEmpty(t, initialPrivateKey)
require.NotEmpty(t, initialCertChain) require.NotEmpty(t, initialCertChain)
for k, v := range env.ConciergeCustomLabels {
require.Equalf(t, v, secret.Labels[k], "expected secret to have label %s: %s", k, v)
}
require.Equal(t, env.ConciergeAppName, secret.Labels["app"])
// Check that the APIService has the same CA. // Check that the APIService has the same CA.
apiService, err := aggregatedClient.ApiregistrationV1().APIServices().Get(ctx, apiServiceName, metav1.GetOptions{}) apiService, err := aggregatedClient.ApiregistrationV1().APIServices().Get(ctx, apiServiceName, metav1.GetOptions{})
@ -115,6 +119,10 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) {
require.NotEqual(t, initialCACert, regeneratedCACert) require.NotEqual(t, initialCACert, regeneratedCACert)
require.NotEqual(t, initialPrivateKey, regeneratedPrivateKey) require.NotEqual(t, initialPrivateKey, regeneratedPrivateKey)
require.NotEqual(t, initialCertChain, regeneratedCertChain) require.NotEqual(t, initialCertChain, regeneratedCertChain)
for k, v := range env.ConciergeCustomLabels {
require.Equalf(t, v, secret.Labels[k], "expected secret to have label `%s: %s`", k, v)
}
require.Equal(t, env.ConciergeAppName, secret.Labels["app"])
// Expect that the APIService was also updated with the new CA. // Expect that the APIService was also updated with the new CA.
aggregatedAPIUpdated := func() bool { aggregatedAPIUpdated := func() bool {

View File

@ -33,8 +33,14 @@ func TestCredentialIssuerConfig(t *testing.T) {
require.Len(t, actualConfigList.Items, 1) require.Len(t, actualConfigList.Items, 1)
actualConfig := actualConfigList.Items[0]
actualStatusKubeConfigInfo := actualConfigList.Items[0].Status.KubeConfigInfo actualStatusKubeConfigInfo := actualConfigList.Items[0].Status.KubeConfigInfo
for k, v := range env.ConciergeCustomLabels {
require.Equalf(t, v, actualConfig.Labels[k], "expected cic to have label `%s: %s`", k, v)
}
require.Equal(t, env.ConciergeAppName, actualConfig.Labels["app"])
// Verify the cluster strategy status based on what's expected of the test cluster's ability to share signing keys. // Verify the cluster strategy status based on what's expected of the test cluster's ability to share signing keys.
actualStatusStrategies := actualConfigList.Items[0].Status.Strategies actualStatusStrategies := actualConfigList.Items[0].Status.Strategies
require.Len(t, actualStatusStrategies, 1) require.Len(t, actualStatusStrategies, 1)

View File

@ -44,6 +44,14 @@ func TestKubeCertAgent(t *testing.T) {
require.NotEmpty(t, originalAgentPods.Items) require.NotEmpty(t, originalAgentPods.Items)
sortPods(originalAgentPods) sortPods(originalAgentPods)
for _, agentPod := range originalAgentPods.Items {
// All agent pods should contain all custom labels
for k, v := range env.ConciergeCustomLabels {
require.Equalf(t, v, agentPod.Labels[k], "expected agent pod to have label `%s: %s`", k, v)
}
require.Equal(t, env.ConciergeAppName, agentPod.Labels["app"])
}
agentPodsReconciled := func() bool { agentPodsReconciled := func() bool {
var currentAgentPods *corev1.PodList var currentAgentPods *corev1.PodList
currentAgentPods, err = kubeClient.CoreV1().Pods(env.ConciergeNamespace).List(ctx, metav1.ListOptions{ currentAgentPods, err = kubeClient.CoreV1().Pods(env.ConciergeNamespace).List(ctx, metav1.ListOptions{

View File

@ -49,6 +49,12 @@ func TestSupervisorOIDCKeys(t *testing.T) {
Get(ctx, updatedOPC.Status.JWKSSecret.Name, metav1.GetOptions{}) Get(ctx, updatedOPC.Status.JWKSSecret.Name, metav1.GetOptions{})
require.NoError(t, err) require.NoError(t, err)
// Ensure that the secret was labelled.
for k, v := range env.SupervisorCustomLabels {
require.Equalf(t, v, secret.Labels[k], "expected secret to have label `%s: %s`", k, v)
}
require.Equal(t, env.SupervisorAppName, secret.Labels["app"])
// Ensure the secret has an active key. // Ensure the secret has an active key.
jwkData, ok := secret.Data["activeJWK"] jwkData, ok := secret.Data["activeJWK"]
require.True(t, ok, "secret is missing active jwk") require.True(t, ok, "secret is missing active jwk")

View File

@ -30,6 +30,8 @@ type TestEnv struct {
SupervisorNamespace string `json:"supervisorNamespace"` SupervisorNamespace string `json:"supervisorNamespace"`
ConciergeAppName string `json:"conciergeAppName"` ConciergeAppName string `json:"conciergeAppName"`
SupervisorAppName string `json:"supervisorAppName"` SupervisorAppName string `json:"supervisorAppName"`
SupervisorCustomLabels map[string]string `json:"supervisorCustomLabels"`
ConciergeCustomLabels map[string]string `json:"conciergeCustomLabels"`
Capabilities map[Capability]bool `json:"capabilities"` Capabilities map[Capability]bool `json:"capabilities"`
TestWebhook idpv1alpha1.WebhookIdentityProviderSpec `json:"testWebhook"` TestWebhook idpv1alpha1.WebhookIdentityProviderSpec `json:"testWebhook"`
SupervisorAddress string `json:"supervisorAddress"` SupervisorAddress string `json:"supervisorAddress"`
@ -89,6 +91,19 @@ func IntegrationEnv(t *testing.T) *TestEnv {
result.SupervisorAddress = needEnv("PINNIPED_TEST_SUPERVISOR_ADDRESS") result.SupervisorAddress = needEnv("PINNIPED_TEST_SUPERVISOR_ADDRESS")
result.TestWebhook.TLS = &idpv1alpha1.TLSSpec{CertificateAuthorityData: needEnv("PINNIPED_TEST_WEBHOOK_CA_BUNDLE")} result.TestWebhook.TLS = &idpv1alpha1.TLSSpec{CertificateAuthorityData: needEnv("PINNIPED_TEST_WEBHOOK_CA_BUNDLE")}
conciergeCustomLabelsYAML := needEnv("PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS")
var conciergeCustomLabels map[string]string
err = yaml.Unmarshal([]byte(conciergeCustomLabelsYAML), &conciergeCustomLabels)
require.NoErrorf(t, err, "PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS must be a YAML map of string to string")
result.ConciergeCustomLabels = conciergeCustomLabels
require.NotEmpty(t, result.ConciergeCustomLabels, "PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS cannot be empty")
supervisorCustomLabelsYAML := needEnv("PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS")
var supervisorCustomLabels map[string]string
err = yaml.Unmarshal([]byte(supervisorCustomLabelsYAML), &supervisorCustomLabels)
require.NoErrorf(t, err, "PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS must be a YAML map of string to string")
result.SupervisorCustomLabels = supervisorCustomLabels
require.NotEmpty(t, result.SupervisorCustomLabels, "PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS cannot be empty")
result.OIDCUpstream.Issuer = needEnv("PINNIPED_TEST_CLI_OIDC_ISSUER") result.OIDCUpstream.Issuer = needEnv("PINNIPED_TEST_CLI_OIDC_ISSUER")
result.OIDCUpstream.ClientID = needEnv("PINNIPED_TEST_CLI_OIDC_CLIENT_ID") result.OIDCUpstream.ClientID = needEnv("PINNIPED_TEST_CLI_OIDC_CLIENT_ID")
result.OIDCUpstream.LocalhostPort, _ = strconv.Atoi(needEnv("PINNIPED_TEST_CLI_OIDC_LOCALHOST_PORT")) result.OIDCUpstream.LocalhostPort, _ = strconv.Atoi(needEnv("PINNIPED_TEST_CLI_OIDC_LOCALHOST_PORT"))