Supervisor controllers apply custom labels to JWKS secrets

Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
Andrew Keesler
2020-10-15 12:40:56 -07:00
committed by Ryan Richard
parent f8e461dfc3
commit 617c5608ca
13 changed files with 188 additions and 52 deletions
-69
View File
@@ -1,69 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package api
// Config contains knobs to setup an instance of Pinniped.
type Config struct {
DiscoveryInfo DiscoveryInfoSpec `json:"discovery"`
APIConfig APIConfigSpec `json:"api"`
NamesConfig NamesConfigSpec `json:"names"`
KubeCertAgentConfig KubeCertAgentSpec `json:"kubeCertAgent"`
Labels map[string]string `json:"labels"`
}
// DiscoveryInfoSpec contains configuration knobs specific to
// pinniped's publishing of discovery information. These values can be
// 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.
type DiscoveryInfoSpec struct {
// URL contains the URL at which pinniped can be contacted.
URL *string `json:"url,omitempty"`
}
// APIConfigSpec contains configuration knobs for the Pinniped API.
//nolint: golint
type APIConfigSpec struct {
ServingCertificateConfig ServingCertificateConfigSpec `json:"servingCertificate"`
}
// NamesConfigSpec configures the names of some Kubernetes resources for Pinniped.
type NamesConfigSpec struct {
ServingCertificateSecret string `json:"servingCertificateSecret"`
CredentialIssuerConfig string `json:"credentialIssuerConfig"`
APIService string `json:"apiService"`
}
// ServingCertificateConfigSpec contains the configuration knobs for the API's
// serving certificate, i.e., the x509 certificate that it uses for the server
// certificate in inbound TLS connections.
type ServingCertificateConfigSpec struct {
// DurationSeconds is the validity period, in seconds, of the API serving
// certificate. By default, the serving certificate is issued for 31536000
// seconds (1 year). This value is also used for the serving certificate's
// CA certificate.
DurationSeconds *int64 `json:"durationSeconds,omitempty"`
// RenewBeforeSeconds is the period of time, in seconds, that Pinniped will
// wait before rotating the serving certificate. This period of time starts
// upon issuance of the serving certificate. This must be less than
// DurationSeconds. By default, Pinniped begins rotation after 23328000
// seconds (about 9 months).
RenewBeforeSeconds *int64 `json:"renewBeforeSeconds,omitempty"`
}
type KubeCertAgentSpec struct {
// NamePrefix is the prefix of the name of the kube-cert-agent pods. For example, if this field is
// set to "some-prefix-", then the name of the pods will look like "some-prefix-blah". The default
// for this value is "pinniped-kube-cert-agent-".
NamePrefix *string `json:"namePrefix,omitempty"`
// Image is the container image that will be used by the kube-cert-agent pod. The container image
// should contain at least 2 binaries: /bin/sleep and cat (somewhere on the $PATH). The default
// for this value is "debian:latest".
Image *string `json:"image"`
// ImagePullSecrets is a list of names of Kubernetes Secret objects that will be used as
// ImagePullSecrets on the kube-cert-agent pods.
ImagePullSecrets []string
}
-119
View File
@@ -1,119 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package config contains functionality to load/store api.Config's from/to
// some source.
package config
import (
"fmt"
"io/ioutil"
"strings"
"sigs.k8s.io/yaml"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/pkg/config/api"
)
const (
aboutAYear = 60 * 60 * 24 * 365
about9Months = 60 * 60 * 24 * 30 * 9
)
// FromPath loads an api.Config from a provided local file path, inserts any
// defaults (from the api.Config documentation), and verifies that the config is
// valid (per the api.Config documentation).
//
// Note! The api.Config file should contain base64-encoded WebhookCABundle data.
// This function will decode that base64-encoded data to PEM bytes to be stored
// in the api.Config.
func FromPath(path string) (*api.Config, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file: %w", err)
}
var config api.Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("decode yaml: %w", err)
}
maybeSetAPIDefaults(&config.APIConfig)
maybeSetKubeCertAgentDefaults(&config.KubeCertAgentConfig)
if err := validateAPI(&config.APIConfig); err != nil {
return nil, fmt.Errorf("validate api: %w", err)
}
if err := validateNames(&config.NamesConfig); err != nil {
return nil, fmt.Errorf("validate names: %w", err)
}
if config.Labels == nil {
config.Labels = make(map[string]string)
}
return &config, nil
}
func maybeSetAPIDefaults(apiConfig *api.APIConfigSpec) {
if apiConfig.ServingCertificateConfig.DurationSeconds == nil {
apiConfig.ServingCertificateConfig.DurationSeconds = int64Ptr(aboutAYear)
}
if apiConfig.ServingCertificateConfig.RenewBeforeSeconds == nil {
apiConfig.ServingCertificateConfig.RenewBeforeSeconds = int64Ptr(about9Months)
}
}
func maybeSetKubeCertAgentDefaults(cfg *api.KubeCertAgentSpec) {
if cfg.NamePrefix == nil {
cfg.NamePrefix = stringPtr("pinniped-kube-cert-agent-")
}
if cfg.Image == nil {
cfg.Image = stringPtr("debian:latest")
}
}
func validateNames(names *api.NamesConfigSpec) error {
missingNames := []string{}
if names == nil {
missingNames = append(missingNames, "servingCertificateSecret", "credentialIssuerConfig", "apiService")
} else {
if names.ServingCertificateSecret == "" {
missingNames = append(missingNames, "servingCertificateSecret")
}
if names.CredentialIssuerConfig == "" {
missingNames = append(missingNames, "credentialIssuerConfig")
}
if names.APIService == "" {
missingNames = append(missingNames, "apiService")
}
}
if len(missingNames) > 0 {
return constable.Error("missing required names: " + strings.Join(missingNames, ", "))
}
return nil
}
func validateAPI(apiConfig *api.APIConfigSpec) error {
if *apiConfig.ServingCertificateConfig.DurationSeconds < *apiConfig.ServingCertificateConfig.RenewBeforeSeconds {
return constable.Error("durationSeconds cannot be smaller than renewBeforeSeconds")
}
if *apiConfig.ServingCertificateConfig.RenewBeforeSeconds <= 0 {
return constable.Error("renewBefore must be positive")
}
return nil
}
func int64Ptr(i int64) *int64 {
return &i
}
func stringPtr(s string) *string {
return &s
}
-211
View File
@@ -1,211 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package config
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/pkg/config/api"
)
func TestFromPath(t *testing.T) {
tests := []struct {
name string
yaml string
wantConfig *api.Config
wantError string
}{
{
name: "Happy",
yaml: here.Doc(`
---
discovery:
url: https://some.discovery/url
api:
servingCertificate:
durationSeconds: 3600
renewBeforeSeconds: 2400
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuerConfig: pinniped-config
apiService: pinniped-api
kubeCertAgentPrefix: kube-cert-agent-prefix
labels:
myLabelKey1: myLabelValue1
myLabelKey2: myLabelValue2
KubeCertAgent:
namePrefix: kube-cert-agent-name-prefix-
image: kube-cert-agent-image
imagePullSecrets: [kube-cert-agent-image-pull-secret]
`),
wantConfig: &api.Config{
DiscoveryInfo: api.DiscoveryInfoSpec{
URL: stringPtr("https://some.discovery/url"),
},
APIConfig: api.APIConfigSpec{
ServingCertificateConfig: api.ServingCertificateConfigSpec{
DurationSeconds: int64Ptr(3600),
RenewBeforeSeconds: int64Ptr(2400),
},
},
NamesConfig: api.NamesConfigSpec{
ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate",
CredentialIssuerConfig: "pinniped-config",
APIService: "pinniped-api",
},
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
},
KubeCertAgentConfig: api.KubeCertAgentSpec{
NamePrefix: stringPtr("kube-cert-agent-name-prefix-"),
Image: stringPtr("kube-cert-agent-image"),
ImagePullSecrets: []string{"kube-cert-agent-image-pull-secret"},
},
},
},
{
name: "When only the required fields are present, causes other fields to be defaulted",
yaml: here.Doc(`
---
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuerConfig: pinniped-config
apiService: pinniped-api
`),
wantConfig: &api.Config{
DiscoveryInfo: api.DiscoveryInfoSpec{
URL: nil,
},
APIConfig: api.APIConfigSpec{
ServingCertificateConfig: api.ServingCertificateConfigSpec{
DurationSeconds: int64Ptr(60 * 60 * 24 * 365), // about a year
RenewBeforeSeconds: int64Ptr(60 * 60 * 24 * 30 * 9), // about 9 months
},
},
NamesConfig: api.NamesConfigSpec{
ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate",
CredentialIssuerConfig: "pinniped-config",
APIService: "pinniped-api",
},
Labels: map[string]string{},
KubeCertAgentConfig: api.KubeCertAgentSpec{
NamePrefix: stringPtr("pinniped-kube-cert-agent-"),
Image: stringPtr("debian:latest"),
},
},
},
{
name: "Empty",
yaml: here.Doc(``),
wantError: "validate names: missing required names: servingCertificateSecret, credentialIssuerConfig, apiService",
},
{
name: "Missing apiService name",
yaml: here.Doc(`
---
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuerConfig: pinniped-config
`),
wantError: "validate names: missing required names: apiService",
},
{
name: "Missing credentialIssuerConfig name",
yaml: here.Doc(`
---
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
apiService: pinniped-api
`),
wantError: "validate names: missing required names: credentialIssuerConfig",
},
{
name: "Missing servingCertificateSecret name",
yaml: here.Doc(`
---
names:
credentialIssuerConfig: pinniped-config
apiService: pinniped-api
`),
wantError: "validate names: missing required names: servingCertificateSecret",
},
{
name: "InvalidDurationRenewBefore",
yaml: here.Doc(`
---
api:
servingCertificate:
durationSeconds: 2400
renewBeforeSeconds: 3600
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuerConfig: pinniped-config
apiService: pinniped-api
`),
wantError: "validate api: durationSeconds cannot be smaller than renewBeforeSeconds",
},
{
name: "NegativeRenewBefore",
yaml: here.Doc(`
---
api:
servingCertificate:
durationSeconds: 2400
renewBeforeSeconds: -10
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuerConfig: pinniped-config
apiService: pinniped-api
`),
wantError: "validate api: renewBefore must be positive",
},
{
name: "ZeroRenewBefore",
yaml: here.Doc(`
---
api:
servingCertificate:
durationSeconds: 2400
renewBeforeSeconds: -10
names:
servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate
credentialIssuerConfig: pinniped-config
apiService: pinniped-api
`),
wantError: "validate api: renewBefore must be positive",
},
}
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())
if test.wantError != "" {
require.EqualError(t, err, test.wantError)
} else {
require.NoError(t, err)
require.Equal(t, test.wantConfig, config)
}
})
}
}