diff --git a/internal/concierge/impersonator/config.go b/internal/concierge/impersonator/config.go new file mode 100644 index 00000000..b2c2d10e --- /dev/null +++ b/internal/concierge/impersonator/config.go @@ -0,0 +1,71 @@ +// Copyright 2021 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package impersonator + +import ( + "fmt" + + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" +) + +type Mode string + +const ( + // Explicitly enable the impersonation proxy. + ModeEnabled Mode = "enabled" + + // Explicitly disable the impersonation proxy. + ModeDisabled Mode = "disabled" + + // Allow the proxy to decide if it should be enabled or disabled based upon the cluster in which it is running. + ModeAuto Mode = "auto" +) + +const ( + ConfigMapDataKey = "config.yaml" +) + +// When specified, both CertificateAuthoritySecretName and TLSSecretName are required. They may be specified to +// both point at the same Secret or to point at different Secrets. +type TLSConfig struct { + // CertificateAuthoritySecretName contains the name of a namespace-local Secret resource. The corresponding Secret + // must contain a key called "ca.crt" whose value is the CA certificate which clients should trust when connecting + // to the impersonation proxy. + CertificateAuthoritySecretName string `json:"certificateAuthoritySecretName"` + + // TLSSecretName contains the name of a namespace-local Secret resource. The corresponding Secret must be of type + // "kubernetes.io/tls" and contain keys called "tls.crt" and "tls.key" whose values are the TLS certificate and + // private key that will be used by the impersonation proxy to serve its endpoints. + TLSSecretName string `json:"tlsSecretName"` +} + +type Config struct { + // Enable or disable the impersonation proxy. Optional. Defaults to ModeAuto. + Mode Mode `json:"mode,omitempty"` + + // The HTTPS URL of the impersonation proxy for clients to use from outside the cluster. Used when creating TLS + // certificates and for clients to discover the endpoint. Optional. When not specified, if the impersonation proxy + // is started, then it will automatically create a LoadBalancer Service and use its ingress as the endpoint. + Endpoint string `json:"endpoint,omitempty"` + + // The TLS configuration of the impersonation proxy's endpoints. Optional. When not specified, a CA and TLS + // certificate will be automatically created based on the Endpoint setting. + TLS *TLSConfig `json:"tls,omitempty"` +} + +func FromConfigMap(configMap *v1.ConfigMap) (*Config, error) { + stringConfig, ok := configMap.Data[ConfigMapDataKey] + if !ok { + return nil, fmt.Errorf(`ConfigMap is missing expected key "%s"`, ConfigMapDataKey) + } + var config Config + if err := yaml.Unmarshal([]byte(stringConfig), &config); err != nil { + return nil, fmt.Errorf("decode yaml: %w", err) + } + if config.Mode == "" { + config.Mode = ModeAuto // set the default value + } + return &config, nil +} diff --git a/internal/concierge/impersonator/config_test.go b/internal/concierge/impersonator/config_test.go new file mode 100644 index 00000000..8406072b --- /dev/null +++ b/internal/concierge/impersonator/config_test.go @@ -0,0 +1,98 @@ +// Copyright 2021 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package impersonator + +import ( + "testing" + + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "go.pinniped.dev/internal/here" +) + +func TestFromConfigMap(t *testing.T) { + tests := []struct { + name string + configMap *v1.ConfigMap + wantConfig *Config + wantError string + }{ + { + name: "fully configured, valid config", + configMap: &v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Data: map[string]string{ + "config.yaml": here.Doc(` + mode: enabled + endpoint: https://proxy.example.com:8443/ + tls: + certificateAuthoritySecretName: my-ca-crt + tlsSecretName: my-tls-certificate-and-key + `), + }, + }, + wantConfig: &Config{ + Mode: "enabled", + Endpoint: "https://proxy.example.com:8443/", + TLS: &TLSConfig{ + CertificateAuthoritySecretName: "my-ca-crt", + TLSSecretName: "my-tls-certificate-and-key", + }, + }, + }, + { + name: "empty, valid config", + configMap: &v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Data: map[string]string{ + "config.yaml": "", + }, + }, + wantConfig: &Config{ + Mode: "auto", + Endpoint: "", + TLS: nil, + }, + }, + { + name: "wrong key in configmap", + configMap: &v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Data: map[string]string{ + "wrong-key": "", + }, + }, + wantError: `ConfigMap is missing expected key "config.yaml"`, + }, + { + name: "illegal yaml in configmap", + configMap: &v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Data: map[string]string{ + "config.yaml": "this is not yaml", + }, + }, + wantError: "decode yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type impersonator.Config", + }, + } + + for _, tt := range tests { + test := tt + t.Run(test.name, func(t *testing.T) { + config, err := FromConfigMap(test.configMap) + require.Equal(t, test.wantConfig, config) + if test.wantError != "" { + require.EqualError(t, err, test.wantError) + } else { + require.NoError(t, err) + } + }) + } +}