Merge pull request #1549 from vmware-tanzu/jtc/tiny-fixups-to-support-1548
Tiny fixups to support #1548
This commit is contained in:
commit
6c329ba56f
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package certauthority implements a simple x509 certificate authority suitable for use in an aggregated API service.
|
// Package certauthority implements a simple x509 certificate authority suitable for use in an aggregated API service.
|
||||||
@ -179,13 +179,13 @@ func (c *CA) IssueServerCert(dnsNames []string, ips []net.IP, ttl time.Duration)
|
|||||||
return c.issueCert(x509.ExtKeyUsageServerAuth, pkix.Name{}, dnsNames, ips, ttl)
|
return c.issueCert(x509.ExtKeyUsageServerAuth, pkix.Name{}, dnsNames, ips, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similar to IssueClientCert, but returning the new cert as a pair of PEM-formatted byte slices
|
// IssueClientCertPEM is similar to IssueClientCert, but returns the new cert as a pair of PEM-formatted byte slices
|
||||||
// for the certificate and private key.
|
// for the certificate and private key.
|
||||||
func (c *CA) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
|
func (c *CA) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
return toPEM(c.IssueClientCert(username, groups, ttl))
|
return toPEM(c.IssueClientCert(username, groups, ttl))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similar to IssueServerCert, but returning the new cert as a pair of PEM-formatted byte slices
|
// IssueServerCertPEM is similar to IssueServerCert, but returns the new cert as a pair of PEM-formatted byte slices
|
||||||
// for the certificate and private key.
|
// for the certificate and private key.
|
||||||
func (c *CA) IssueServerCertPEM(dnsNames []string, ips []net.IP, ttl time.Duration) ([]byte, []byte, error) {
|
func (c *CA) IssueServerCertPEM(dnsNames []string, ips []net.IP, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
return toPEM(c.IssueServerCert(dnsNames, ips, ttl))
|
return toPEM(c.IssueServerCert(dnsNames, ips, ttl))
|
||||||
@ -260,7 +260,7 @@ func toPEM(cert *tls.Certificate, err error) ([]byte, []byte, error) {
|
|||||||
return certPEM, keyPEM, nil
|
return certPEM, keyPEM, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode a tls.Certificate into a private key PEM and a cert chain PEM.
|
// ToPEM encodes a tls.Certificate into a private key PEM and a cert chain PEM.
|
||||||
func ToPEM(cert *tls.Certificate) ([]byte, []byte, error) {
|
func ToPEM(cert *tls.Certificate) ([]byte, []byte, error) {
|
||||||
// Encode the certificate(s) to PEM.
|
// Encode the certificate(s) to PEM.
|
||||||
certPEMBlocks := make([][]byte, 0, len(cert.Certificate))
|
certPEMBlocks := make([][]byte, 0, len(cert.Certificate))
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -20,60 +20,65 @@ import (
|
|||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadFromFiles(t *testing.T, certPath string, keyPath string) (*CA, error) {
|
var (
|
||||||
t.Helper()
|
//go:embed testdata/empty
|
||||||
|
empty string
|
||||||
certPEM, err := os.ReadFile(certPath)
|
//go:embed testdata/invalid
|
||||||
require.NoError(t, err)
|
invalid string
|
||||||
|
//go:embed testdata/multiple.crt
|
||||||
keyPEM, err := os.ReadFile(keyPath)
|
multiple string
|
||||||
require.NoError(t, err)
|
//go:embed testdata/test.crt
|
||||||
|
testCert string
|
||||||
ca, err := Load(string(certPEM), string(keyPEM))
|
//go:embed testdata/test.key
|
||||||
return ca, err
|
testKey string
|
||||||
}
|
//go:embed testdata/test2.key
|
||||||
|
testKey2 string
|
||||||
|
)
|
||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
func TestLoad(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
certPath string
|
cert string
|
||||||
keyPath string
|
key string
|
||||||
wantErr string
|
wantErr string
|
||||||
|
test []byte
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty key",
|
name: "empty key",
|
||||||
certPath: "./testdata/test.crt",
|
cert: testCert,
|
||||||
keyPath: "./testdata/empty",
|
key: empty,
|
||||||
wantErr: "could not load CA: tls: failed to find any PEM data in key input",
|
wantErr: "could not load CA: tls: failed to find any PEM data in key input",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid key",
|
name: "invalid key",
|
||||||
certPath: "./testdata/test.crt",
|
cert: testCert,
|
||||||
keyPath: "./testdata/invalid",
|
key: invalid,
|
||||||
wantErr: "could not load CA: tls: failed to find any PEM data in key input",
|
wantErr: "could not load CA: tls: failed to find any PEM data in key input",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mismatched cert and key",
|
name: "mismatched cert and key",
|
||||||
certPath: "./testdata/test.crt",
|
cert: testCert,
|
||||||
keyPath: "./testdata/test2.key",
|
key: testKey2,
|
||||||
wantErr: "could not load CA: tls: private key does not match public key",
|
wantErr: "could not load CA: tls: private key does not match public key",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple certs",
|
name: "multiple certs",
|
||||||
certPath: "./testdata/multiple.crt",
|
cert: multiple,
|
||||||
keyPath: "./testdata/test.key",
|
key: testKey,
|
||||||
wantErr: "invalid CA certificate: expected a single certificate, found 2 certificates",
|
wantErr: "invalid CA certificate: expected a single certificate, found 2 certificates",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success",
|
||||||
certPath: "./testdata/test.crt",
|
cert: testCert,
|
||||||
keyPath: "./testdata/test.key",
|
key: testKey,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ca, err := loadFromFiles(t, tt.certPath, tt.keyPath)
|
t.Parallel()
|
||||||
|
|
||||||
|
ca, err := Load(tt.cert, tt.key)
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.EqualError(t, err, tt.wantErr)
|
require.EqualError(t, err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -226,7 +231,7 @@ func TestIssue(t *testing.T) {
|
|||||||
|
|
||||||
now := time.Date(2020, 7, 10, 12, 41, 12, 1234, time.UTC)
|
now := time.Date(2020, 7, 10, 12, 41, 12, 1234, time.UTC)
|
||||||
|
|
||||||
realCA, err := loadFromFiles(t, "./testdata/test.crt", "./testdata/test.key")
|
realCA, err := Load(testCert, testKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package server is the command line entry point for pinniped-concierge.
|
// Package server is the command line entry point for pinniped-concierge.
|
||||||
@ -118,17 +118,17 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
|
|
||||||
// This cert provider will provide certs to the API server and will
|
// This cert provider will provide certs to the API server and will
|
||||||
// be mutated by a controller to keep the certs up to date with what
|
// be mutated by a controller to keep the certs up to date with what
|
||||||
// is stored in a k8s Secret. Therefore it also effectively acting as
|
// is stored in a k8s Secret. Therefore, it acts as an in-memory cache
|
||||||
// an in-memory cache of what is stored in the k8s Secret, helping to
|
// of what is stored in the k8s Secret, helping to keep incoming requests
|
||||||
// keep incoming requests fast.
|
// fast.
|
||||||
dynamicServingCertProvider := dynamiccert.NewServingCert("concierge-serving-cert")
|
dynamicServingCertProvider := dynamiccert.NewServingCert("concierge-serving-cert")
|
||||||
|
|
||||||
// This cert provider will be used to provide the Kube signing key to the
|
// This cert provider will be used to provide the Kube signing key to the
|
||||||
// cert issuer used to issue certs to Pinniped clients wishing to login.
|
// cert issuer used to issue certs to Pinniped clients wishing to log in.
|
||||||
dynamicSigningCertProvider := dynamiccert.NewCA("concierge-kube-signing-cert")
|
dynamicSigningCertProvider := dynamiccert.NewCA("concierge-kube-signing-cert")
|
||||||
|
|
||||||
// This cert provider will be used to provide the impersonation proxy signing key to the
|
// This cert provider will be used to provide the impersonation proxy signing key to the
|
||||||
// cert issuer used to issue certs to Pinniped clients wishing to login.
|
// cert issuer used to issue certs to Pinniped clients wishing to log in.
|
||||||
impersonationProxySigningCertProvider := dynamiccert.NewCA("impersonation-proxy-signing-cert")
|
impersonationProxySigningCertProvider := dynamiccert.NewCA("impersonation-proxy-signing-cert")
|
||||||
|
|
||||||
// Get the "real" name of the login concierge API group (i.e., the API group name with the
|
// Get the "real" name of the login concierge API group (i.e., the API group name with the
|
||||||
@ -256,7 +256,8 @@ func getAggregatedAPIServerConfig(
|
|||||||
return apiServerConfig, nil
|
return apiServerConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() error { // return an error instead of plog.Fatal to allow defer statements to run
|
// main returns an error instead of calling plog.Fatal to allow defer statements to run.
|
||||||
|
func main() error {
|
||||||
defer plog.Setup()()
|
defer plog.Setup()()
|
||||||
|
|
||||||
// Dump out the time since compile (mostly useful for benchmarking our local development cycle latency).
|
// Dump out the time since compile (mostly useful for benchmarking our local development cycle latency).
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package concierge contains functionality to load/store Config's from/to
|
// Package concierge contains functionality to load/store Config's from/to
|
||||||
@ -35,7 +35,7 @@ const (
|
|||||||
impersonationProxyPortDefault = 8444
|
impersonationProxyPortDefault = 8444
|
||||||
)
|
)
|
||||||
|
|
||||||
// FromPath loads an Config from a provided local file path, inserts any
|
// FromPath loads a Config from a provided local file path, inserts any
|
||||||
// defaults (from the Config documentation), and verifies that the config is
|
// defaults (from the Config documentation), and verifies that the config is
|
||||||
// valid (per the Config documentation).
|
// valid (per the Config documentation).
|
||||||
//
|
//
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package concierge
|
package concierge
|
||||||
|
|
||||||
import "go.pinniped.dev/internal/plog"
|
import "go.pinniped.dev/internal/plog"
|
||||||
|
|
||||||
// Config contains knobs to setup an instance of the Pinniped Concierge.
|
// Config contains knobs to set up 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"`
|
||||||
@ -21,7 +21,7 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package impersonatorconfig
|
package impersonatorconfig
|
||||||
@ -285,12 +285,10 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context, cre
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var impersonationCA *certauthority.CA
|
var impersonationCABundle []byte
|
||||||
if c.shouldHaveImpersonator(impersonationSpec) {
|
if c.shouldHaveImpersonator(impersonationSpec) {
|
||||||
if impersonationCA, err = c.ensureCASecretIsCreated(ctx); err != nil {
|
impersonationCABundle, err = c.ensureCAAndTLSSecrets(ctx, nameInfo)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
|
||||||
if err = c.ensureTLSSecret(ctx, nameInfo, impersonationCA); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -300,7 +298,7 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context, cre
|
|||||||
c.clearTLSSecret()
|
c.clearTLSSecret()
|
||||||
}
|
}
|
||||||
|
|
||||||
credentialIssuerStrategyResult := c.doSyncResult(nameInfo, impersonationSpec, impersonationCA)
|
credentialIssuerStrategyResult := c.doSyncResult(nameInfo, impersonationSpec, impersonationCABundle)
|
||||||
|
|
||||||
if c.shouldHaveImpersonator(impersonationSpec) {
|
if c.shouldHaveImpersonator(impersonationSpec) {
|
||||||
if err = c.loadSignerCA(); err != nil {
|
if err = c.loadSignerCA(); err != nil {
|
||||||
@ -313,6 +311,25 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context, cre
|
|||||||
return credentialIssuerStrategyResult, nil
|
return credentialIssuerStrategyResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *impersonatorConfigController) ensureCAAndTLSSecrets(ctx context.Context, nameInfo *certNameInfo) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
impersonationCA *certauthority.CA
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if impersonationCA, err = c.ensureCASecretIsCreated(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = c.ensureTLSSecret(ctx, nameInfo, impersonationCA); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if impersonationCA != nil {
|
||||||
|
return impersonationCA.Bundle(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *impersonatorConfigController) loadImpersonationProxyConfiguration(credIssuer *v1alpha1.CredentialIssuer) (*v1alpha1.ImpersonationProxySpec, error) {
|
func (c *impersonatorConfigController) loadImpersonationProxyConfiguration(credIssuer *v1alpha1.CredentialIssuer) (*v1alpha1.ImpersonationProxySpec, error) {
|
||||||
// Make a copy of the spec since we got this object from informer cache.
|
// Make a copy of the spec since we got this object from informer cache.
|
||||||
spec := credIssuer.Spec.DeepCopy().ImpersonationProxy
|
spec := credIssuer.Spec.DeepCopy().ImpersonationProxy
|
||||||
@ -707,7 +724,7 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !nameInfo.ready {
|
if !nameInfo.ready {
|
||||||
// We currently have a secret but we are waiting for a load balancer to be assigned an ingress, so
|
// We currently have a secret, but we are waiting for a load balancer to be assigned an ingress, so
|
||||||
// our current secret must be old/unwanted.
|
// our current secret must be old/unwanted.
|
||||||
if err = c.ensureTLSSecretIsRemoved(ctx); err != nil {
|
if err = c.ensureTLSSecretIsRemoved(ctx); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -1018,7 +1035,7 @@ func (c *impersonatorConfigController) clearSignerCA() {
|
|||||||
c.impersonationSigningCertProvider.UnsetCertKeyContent()
|
c.impersonationSigningCertProvider.UnsetCertKeyContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, config *v1alpha1.ImpersonationProxySpec, ca *certauthority.CA) *v1alpha1.CredentialIssuerStrategy {
|
func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, config *v1alpha1.ImpersonationProxySpec, caBundle []byte) *v1alpha1.CredentialIssuerStrategy {
|
||||||
switch {
|
switch {
|
||||||
case c.disabledExplicitly(config):
|
case c.disabledExplicitly(config):
|
||||||
return &v1alpha1.CredentialIssuerStrategy{
|
return &v1alpha1.CredentialIssuerStrategy{
|
||||||
@ -1055,7 +1072,7 @@ func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, conf
|
|||||||
Type: v1alpha1.ImpersonationProxyFrontendType,
|
Type: v1alpha1.ImpersonationProxyFrontendType,
|
||||||
ImpersonationProxyInfo: &v1alpha1.ImpersonationProxyInfo{
|
ImpersonationProxyInfo: &v1alpha1.ImpersonationProxyInfo{
|
||||||
Endpoint: "https://" + nameInfo.clientEndpoint,
|
Endpoint: "https://" + nameInfo.clientEndpoint,
|
||||||
CertificateAuthorityData: base64.StdEncoding.EncodeToString(ca.Bundle()),
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString(caBundle),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package controllerlib
|
package controllerlib
|
||||||
@ -39,8 +39,8 @@ func (c *controllerManager) WithController(controller Controller, workers int) M
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start will run all managed controllers and block until all controllers shutdown.
|
// Start will run all managed controllers and block until all controllers have shut down.
|
||||||
// When the context passed is cancelled, all controllers are signalled to shutdown.
|
// When the context passed is cancelled, all controllers are signalled to shut down.
|
||||||
func (c *controllerManager) Start(ctx context.Context) {
|
func (c *controllerManager) Start(ctx context.Context) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(c.controllers))
|
wg.Add(len(c.controllers))
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package issuer
|
package issuer
|
||||||
@ -38,15 +38,14 @@ func (c ClientCertIssuers) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c ClientCertIssuers) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
|
func (c ClientCertIssuers) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
var errs []error
|
errs := make([]error, 0, len(c))
|
||||||
|
|
||||||
for _, issuer := range c {
|
for _, issuer := range c {
|
||||||
certPEM, keyPEM, err := issuer.IssueClientCertPEM(username, groups, ttl)
|
certPEM, keyPEM, err := issuer.IssueClientCertPEM(username, groups, ttl)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
errs = append(errs, fmt.Errorf("%s failed to issue client cert: %w", issuer.Name(), err))
|
return certPEM, keyPEM, nil
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
return certPEM, keyPEM, nil
|
errs = append(errs, fmt.Errorf("%s failed to issue client cert: %w", issuer.Name(), err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := errors.NewAggregate(errs); err != nil {
|
if err := errors.NewAggregate(errs); err != nil {
|
||||||
|
169
internal/issuer/issuer_test.go
Normal file
169
internal/issuer/issuer_test.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2023 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package issuer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/mocks/issuermocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestName(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
buildIssuerMocks func() ClientCertIssuers
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty issuers",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers { return ClientCertIssuers{} },
|
||||||
|
want: "empty-client-cert-issuers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "foo issuer",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers {
|
||||||
|
fooClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
fooClientCertIssuer.EXPECT().Name().Return("foo")
|
||||||
|
|
||||||
|
return ClientCertIssuers{fooClientCertIssuer}
|
||||||
|
},
|
||||||
|
want: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "foo and bar issuers",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers {
|
||||||
|
fooClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
fooClientCertIssuer.EXPECT().Name().Return("foo")
|
||||||
|
|
||||||
|
barClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
barClientCertIssuer.EXPECT().Name().Return("bar")
|
||||||
|
|
||||||
|
return ClientCertIssuers{fooClientCertIssuer, barClientCertIssuer}
|
||||||
|
},
|
||||||
|
want: "foo,bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tTemp := range tests {
|
||||||
|
testcase := tTemp
|
||||||
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
name := testcase.buildIssuerMocks().Name()
|
||||||
|
require.Equal(t, testcase.want, name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssueClientCertPEM(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
buildIssuerMocks func() ClientCertIssuers
|
||||||
|
wantErrorMessage string
|
||||||
|
wantCert []byte
|
||||||
|
wantKey []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty issuers",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers { return ClientCertIssuers{} },
|
||||||
|
wantErrorMessage: "failed to issue cert",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issuers with error",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers {
|
||||||
|
errClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
errClientCertIssuer.EXPECT().Name().Return("error cert issuer")
|
||||||
|
errClientCertIssuer.EXPECT().
|
||||||
|
IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second).
|
||||||
|
Return(nil, nil, errors.New("error from wrapped cert issuer"))
|
||||||
|
return ClientCertIssuers{errClientCertIssuer}
|
||||||
|
},
|
||||||
|
wantErrorMessage: "error cert issuer failed to issue client cert: error from wrapped cert issuer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid issuer",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers {
|
||||||
|
validClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
validClientCertIssuer.EXPECT().
|
||||||
|
IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second).
|
||||||
|
Return([]byte("cert"), []byte("key"), nil)
|
||||||
|
return ClientCertIssuers{validClientCertIssuer}
|
||||||
|
},
|
||||||
|
wantCert: []byte("cert"),
|
||||||
|
wantKey: []byte("key"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fallthrough issuer",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers {
|
||||||
|
errClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
errClientCertIssuer.EXPECT().Name().Return("error cert issuer")
|
||||||
|
errClientCertIssuer.EXPECT().
|
||||||
|
IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second).
|
||||||
|
Return(nil, nil, errors.New("error from wrapped cert issuer"))
|
||||||
|
|
||||||
|
validClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
validClientCertIssuer.EXPECT().
|
||||||
|
IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second).
|
||||||
|
Return([]byte("cert"), []byte("key"), nil)
|
||||||
|
return ClientCertIssuers{
|
||||||
|
errClientCertIssuer,
|
||||||
|
validClientCertIssuer,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantCert: []byte("cert"),
|
||||||
|
wantKey: []byte("key"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple error issuers",
|
||||||
|
buildIssuerMocks: func() ClientCertIssuers {
|
||||||
|
err1ClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
err1ClientCertIssuer.EXPECT().Name().Return("error1 cert issuer")
|
||||||
|
err1ClientCertIssuer.EXPECT().
|
||||||
|
IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second).
|
||||||
|
Return(nil, nil, errors.New("error1 from wrapped cert issuer"))
|
||||||
|
|
||||||
|
err2ClientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
|
err2ClientCertIssuer.EXPECT().Name().Return("error2 cert issuer")
|
||||||
|
err2ClientCertIssuer.EXPECT().
|
||||||
|
IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second).
|
||||||
|
Return(nil, nil, errors.New("error2 from wrapped cert issuer"))
|
||||||
|
|
||||||
|
return ClientCertIssuers{
|
||||||
|
err1ClientCertIssuer,
|
||||||
|
err2ClientCertIssuer,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantErrorMessage: "[error1 cert issuer failed to issue client cert: error1 from wrapped cert issuer, error2 cert issuer failed to issue client cert: error2 from wrapped cert issuer]",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tTemp := range tests {
|
||||||
|
testcase := tTemp
|
||||||
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
certPEM, keyPEM, err := testcase.buildIssuerMocks().
|
||||||
|
IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second)
|
||||||
|
|
||||||
|
if testcase.wantErrorMessage != "" {
|
||||||
|
require.ErrorContains(t, err, testcase.wantErrorMessage)
|
||||||
|
require.Empty(t, certPEM)
|
||||||
|
require.Empty(t, keyPEM)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, testcase.wantCert, certPEM)
|
||||||
|
require.Equal(t, testcase.wantKey, keyPEM)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package provider
|
package provider
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
"go.pinniped.dev/internal/constable"
|
"go.pinniped.dev/internal/constable"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FederationDomainIssuer represents all of the settings and state for a downstream OIDC provider
|
// FederationDomainIssuer represents all the settings and state for a downstream OIDC provider
|
||||||
// as defined by a FederationDomain.
|
// as defined by a FederationDomain.
|
||||||
type FederationDomainIssuer struct {
|
type FederationDomainIssuer struct {
|
||||||
issuer string
|
issuer string
|
||||||
@ -19,6 +19,8 @@ type FederationDomainIssuer struct {
|
|||||||
issuerPath string
|
issuerPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFederationDomainIssuer returns a FederationDomainIssuer.
|
||||||
|
// Performs validation, and returns any error from validation.
|
||||||
func NewFederationDomainIssuer(issuer string) (*FederationDomainIssuer, error) {
|
func NewFederationDomainIssuer(issuer string) (*FederationDomainIssuer, error) {
|
||||||
p := FederationDomainIssuer{issuer: issuer}
|
p := FederationDomainIssuer{issuer: issuer}
|
||||||
err := p.validate()
|
err := p.validate()
|
||||||
@ -42,6 +44,10 @@ func (p *FederationDomainIssuer) validate() error {
|
|||||||
return constable.Error(`issuer must have "https" scheme`)
|
return constable.Error(`issuer must have "https" scheme`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if issuerURL.Hostname() == "" {
|
||||||
|
return constable.Error(`issuer must have a hostname`)
|
||||||
|
}
|
||||||
|
|
||||||
if issuerURL.User != nil {
|
if issuerURL.User != nil {
|
||||||
return constable.Error(`issuer must not have username or password`)
|
return constable.Error(`issuer must not have username or password`)
|
||||||
}
|
}
|
||||||
@ -64,14 +70,17 @@ func (p *FederationDomainIssuer) validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issuer returns the issuer.
|
||||||
func (p *FederationDomainIssuer) Issuer() string {
|
func (p *FederationDomainIssuer) Issuer() string {
|
||||||
return p.issuer
|
return p.issuer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IssuerHost returns the issuerHost.
|
||||||
func (p *FederationDomainIssuer) IssuerHost() string {
|
func (p *FederationDomainIssuer) IssuerHost() string {
|
||||||
return p.issuerHost
|
return p.issuerHost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IssuerPath returns the issuerPath.
|
||||||
func (p *FederationDomainIssuer) IssuerPath() string {
|
func (p *FederationDomainIssuer) IssuerPath() string {
|
||||||
return p.issuerPath
|
return p.issuerPath
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package provider
|
package provider
|
||||||
@ -20,6 +20,16 @@ func TestFederationDomainIssuerValidations(t *testing.T) {
|
|||||||
issuer: "",
|
issuer: "",
|
||||||
wantError: "federation domain must have an issuer",
|
wantError: "federation domain must have an issuer",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "returns url.Parse errors",
|
||||||
|
issuer: "https://example.com" + string(byte(0x7f)),
|
||||||
|
wantError: "could not parse issuer as URL: parse \"https://example.com\\x7f\": net/url: invalid control character in URL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no hostname",
|
||||||
|
issuer: "https://",
|
||||||
|
wantError: `issuer must have a hostname`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "no scheme",
|
name: "no scheme",
|
||||||
issuer: "tuna.com",
|
issuer: "tuna.com",
|
||||||
|
@ -59,7 +59,7 @@ Pinniped uses [ytt](https://carvel.dev/ytt/) from [Carvel](https://carvel.dev/)
|
|||||||
1. Customize configuration parameters:
|
1. Customize configuration parameters:
|
||||||
|
|
||||||
- See the [default values](http://github.com/vmware-tanzu/pinniped/tree/main/deploy/supervisor/values.yaml) for documentation about individual configuration parameters.
|
- See the [default values](http://github.com/vmware-tanzu/pinniped/tree/main/deploy/supervisor/values.yaml) for documentation about individual configuration parameters.
|
||||||
For example, you can change the number of Concierge pods by setting `replicas` or apply custom annotations to the impersonation proxy service using `impersonation_proxy_spec`.
|
For example, you can change the number of Supervisor pods by setting `replicas` or install into a non-default namespace using `into_namespace`.
|
||||||
|
|
||||||
- In a different directory, create a new YAML file to contain your site-specific configuration. For example, you might call this file `site/dev-env.yaml`.
|
- In a different directory, create a new YAML file to contain your site-specific configuration. For example, you might call this file `site/dev-env.yaml`.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user