certauthority.go: Refactor issuing client versus server certs
We were previously issuing both client certs and server certs with both extended key usages included. Split the Issue*() methods into separate methods for issuing server certs versus client certs so they can have different extended key usages tailored for each use case. Also took the opportunity to clean up the parameters of the Issue*() methods and New() methods to more closely match how we prefer to call them. We were always only passing the common name part of the pkix.Name to New(), so now the New() method just takes the common name as a string. When making a server cert, we don't need to set the deprecated common name field, so remove that param. When making a client cert, we're always making it in the format expected by the Kube API server, so just accept the username and group as parameters directly.
This commit is contained in:
parent
5e4746e96b
commit
c82f568b2c
@ -8,7 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -464,10 +463,10 @@ func newCertProvider(t *testing.T) (dynamiccert.Provider, []byte, string) {
|
|||||||
|
|
||||||
serverName := "local-user-authenticator"
|
serverName := "local-user-authenticator"
|
||||||
|
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: serverName + " CA"}, time.Hour*24)
|
ca, err := certauthority.New(serverName+" CA", time.Hour*24)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cert, err := ca.Issue(pkix.Name{CommonName: serverName}, []string{serverName}, nil, time.Hour*24)
|
cert, err := ca.IssueServerCert([]string{serverName}, nil, time.Hour*24)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
certPEM, keyPEM, err := certauthority.ToPEM(cert)
|
certPEM, keyPEM, err := certauthority.ToPEM(cert)
|
||||||
|
@ -5,7 +5,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -51,7 +50,7 @@ func TestConciergeModeFlag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCABundleFlag(t *testing.T) {
|
func TestCABundleFlag(t *testing.T) {
|
||||||
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
|
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tmpdir := testutil.TempDir(t)
|
tmpdir := testutil.TempDir(t)
|
||||||
emptyFilePath := filepath.Join(tmpdir, "empty")
|
emptyFilePath := filepath.Join(tmpdir, "empty")
|
||||||
|
@ -5,7 +5,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -30,13 +29,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetKubeconfig(t *testing.T) {
|
func TestGetKubeconfig(t *testing.T) {
|
||||||
testOIDCCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
|
testOIDCCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tmpdir := testutil.TempDir(t)
|
tmpdir := testutil.TempDir(t)
|
||||||
testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
||||||
require.NoError(t, ioutil.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600))
|
require.NoError(t, ioutil.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600))
|
||||||
|
|
||||||
testConciergeCA, err := certauthority.New(pkix.Name{CommonName: "Test Concierge CA"}, 1*time.Hour)
|
testConciergeCA, err := certauthority.New("Test Concierge CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testConciergeCABundlePath := filepath.Join(tmpdir, "testconciergeca.pem")
|
testConciergeCABundlePath := filepath.Join(tmpdir, "testconciergeca.pem")
|
||||||
require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, testConciergeCA.Bundle(), 0600))
|
require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, testConciergeCA.Bundle(), 0600))
|
||||||
|
@ -6,7 +6,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -29,7 +28,7 @@ import (
|
|||||||
func TestLoginOIDCCommand(t *testing.T) {
|
func TestLoginOIDCCommand(t *testing.T) {
|
||||||
cfgDir := mustGetConfigDir()
|
cfgDir := mustGetConfigDir()
|
||||||
|
|
||||||
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
|
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tmpdir := testutil.TempDir(t)
|
tmpdir := testutil.TempDir(t)
|
||||||
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
||||||
|
@ -6,7 +6,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -24,7 +23,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestLoginStaticCommand(t *testing.T) {
|
func TestLoginStaticCommand(t *testing.T) {
|
||||||
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
|
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tmpdir := testutil.TempDir(t)
|
tmpdir := testutil.TempDir(t)
|
||||||
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
||||||
|
@ -89,13 +89,13 @@ func Load(certPEM string, keyPEM string) (*CA, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New generates a fresh certificate authority with the given subject and ttl.
|
// New generates a fresh certificate authority with the given Common Name and TTL.
|
||||||
func New(subject pkix.Name, ttl time.Duration) (*CA, error) {
|
func New(commonName string, ttl time.Duration) (*CA, error) {
|
||||||
return newInternal(subject, ttl, secureEnv())
|
return newInternal(commonName, ttl, secureEnv())
|
||||||
}
|
}
|
||||||
|
|
||||||
// newInternal is the internal guts of New, broken out for easier testing.
|
// newInternal is the internal guts of New, broken out for easier testing.
|
||||||
func newInternal(subject pkix.Name, ttl time.Duration, env env) (*CA, error) {
|
func newInternal(commonName string, ttl time.Duration, env env) (*CA, error) {
|
||||||
ca := CA{env: env}
|
ca := CA{env: env}
|
||||||
// Generate a random serial for the CA
|
// Generate a random serial for the CA
|
||||||
serialNumber, err := randomSerial(env.serialRNG)
|
serialNumber, err := randomSerial(env.serialRNG)
|
||||||
@ -118,7 +118,7 @@ func newInternal(subject pkix.Name, ttl time.Duration, env env) (*CA, error) {
|
|||||||
// Create CA cert template
|
// Create CA cert template
|
||||||
caTemplate := x509.Certificate{
|
caTemplate := x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
Subject: subject,
|
Subject: pkix.Name{CommonName: commonName},
|
||||||
NotBefore: notBefore,
|
NotBefore: notBefore,
|
||||||
NotAfter: notAfter,
|
NotAfter: notAfter,
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
@ -160,8 +160,31 @@ func (c *CA) Pool() *x509.CertPool {
|
|||||||
return pool
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue a new server certificate for the given identity and duration.
|
// IssueClientCert issues a new client certificate with username and groups included in the Kube-style
|
||||||
func (c *CA) Issue(subject pkix.Name, dnsNames []string, ips []net.IP, ttl time.Duration) (*tls.Certificate, error) {
|
// certificate subject for the given identity and duration.
|
||||||
|
func (c *CA) IssueClientCert(username string, groups []string, ttl time.Duration) (*tls.Certificate, error) {
|
||||||
|
return c.issueCert(x509.ExtKeyUsageClientAuth, pkix.Name{CommonName: username, Organization: groups}, nil, nil, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueServerCert issues a new server certificate for the given identity and duration.
|
||||||
|
// The dnsNames and ips are each optional, but at least one of them should be specified.
|
||||||
|
func (c *CA) IssueServerCert(dnsNames []string, ips []net.IP, ttl time.Duration) (*tls.Certificate, error) {
|
||||||
|
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
|
||||||
|
// for the certificate and private key.
|
||||||
|
func (c *CA) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
|
return toPEM(c.IssueClientCert(username, groups, ttl))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to IssueServerCert, but returning the new cert as a pair of PEM-formatted byte slices
|
||||||
|
// for the certificate and private key.
|
||||||
|
func (c *CA) IssueServerCertPEM(dnsNames []string, ips []net.IP, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
|
return toPEM(c.IssueServerCert(dnsNames, ips, ttl))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CA) issueCert(extKeyUsage x509.ExtKeyUsage, subject pkix.Name, dnsNames []string, ips []net.IP, ttl time.Duration) (*tls.Certificate, error) {
|
||||||
// Choose a random 128 bit serial number.
|
// Choose a random 128 bit serial number.
|
||||||
serialNumber, err := randomSerial(c.env.serialRNG)
|
serialNumber, err := randomSerial(c.env.serialRNG)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -187,13 +210,11 @@ func (c *CA) Issue(subject pkix.Name, dnsNames []string, ips []net.IP, ttl time.
|
|||||||
|
|
||||||
// Sign a cert, getting back the DER-encoded certificate bytes.
|
// Sign a cert, getting back the DER-encoded certificate bytes.
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
NotBefore: notBefore,
|
NotBefore: notBefore,
|
||||||
NotAfter: notAfter,
|
NotAfter: notAfter,
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
ExtKeyUsage: []x509.ExtKeyUsage{extKeyUsage},
|
||||||
// TODO split this function into two funcs that handle client and serving certs differently
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
IsCA: false,
|
IsCA: false,
|
||||||
DNSNames: dnsNames,
|
DNSNames: dnsNames,
|
||||||
@ -218,14 +239,8 @@ func (c *CA) Issue(subject pkix.Name, dnsNames []string, ips []net.IP, ttl time.
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssuePEM issues a new server certificate for the given identity and duration, returning it as a pair of
|
|
||||||
// PEM-formatted byte slices for the certificate and private key.
|
|
||||||
func (c *CA) IssuePEM(subject pkix.Name, dnsNames []string, ips []net.IP, ttl time.Duration) ([]byte, []byte, error) {
|
|
||||||
return toPEM(c.Issue(subject, dnsNames, ips, ttl))
|
|
||||||
}
|
|
||||||
|
|
||||||
func toPEM(cert *tls.Certificate, err error) ([]byte, []byte, error) {
|
func toPEM(cert *tls.Certificate, err error) ([]byte, []byte, error) {
|
||||||
// If the wrapped Issue() returned an error, pass it back.
|
// If the wrapped IssueServerCert() returned an error, pass it back.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -17,6 +16,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadFromFiles(t *testing.T, certPath string, keyPath string) (*CA, error) {
|
func loadFromFiles(t *testing.T, certPath string, keyPath string) (*CA, error) {
|
||||||
@ -87,7 +88,7 @@ func TestLoad(t *testing.T) {
|
|||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
ca, err := New(pkix.Name{CommonName: "Test CA"}, time.Minute)
|
ca, err := New("Test CA", time.Minute)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, ca)
|
require.NotNil(t, ca)
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ func TestNewInternal(t *testing.T) {
|
|||||||
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) {
|
||||||
got, err := newInternal(pkix.Name{CommonName: "Test CA"}, tt.ttl, tt.env)
|
got, err := newInternal("Test CA", tt.ttl, tt.env)
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.EqualError(t, err, tt.wantErr)
|
require.EqualError(t, err, tt.wantErr)
|
||||||
require.Nil(t, got)
|
require.Nil(t, got)
|
||||||
@ -184,7 +185,7 @@ func TestBundle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrivateKeyToPEM(t *testing.T) {
|
func TestPrivateKeyToPEM(t *testing.T) {
|
||||||
ca, err := New(pkix.Name{CommonName: "Test CA"}, time.Hour)
|
ca, err := New("Test CA", time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
keyPEM, err := ca.PrivateKeyToPEM()
|
keyPEM, err := ca.PrivateKeyToPEM()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -201,7 +202,7 @@ func TestPrivateKeyToPEM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPool(t *testing.T) {
|
func TestPool(t *testing.T) {
|
||||||
ca, err := New(pkix.Name{CommonName: "test"}, 1*time.Hour)
|
ca, err := New("test", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pool := ca.Pool()
|
pool := ca.Pool()
|
||||||
@ -220,6 +221,8 @@ func (e *errSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue(t *testing.T) {
|
func TestIssue(t *testing.T) {
|
||||||
|
const numRandBytes = 64 * 2 // each call to issue a cert will consume 64 bytes from the reader
|
||||||
|
|
||||||
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 := loadFromFiles(t, "./testdata/test.crt", "./testdata/test.key")
|
||||||
@ -243,7 +246,7 @@ func TestIssue(t *testing.T) {
|
|||||||
name: "failed to generate keypair",
|
name: "failed to generate keypair",
|
||||||
ca: CA{
|
ca: CA{
|
||||||
env: env{
|
env: env{
|
||||||
serialRNG: strings.NewReader(strings.Repeat("x", 64)),
|
serialRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
keygenRNG: strings.NewReader(""),
|
keygenRNG: strings.NewReader(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -253,8 +256,8 @@ func TestIssue(t *testing.T) {
|
|||||||
name: "invalid CA certificate",
|
name: "invalid CA certificate",
|
||||||
ca: CA{
|
ca: CA{
|
||||||
env: env{
|
env: env{
|
||||||
serialRNG: strings.NewReader(strings.Repeat("x", 64)),
|
serialRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
keygenRNG: strings.NewReader(strings.Repeat("x", 64)),
|
keygenRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
clock: func() time.Time { return now },
|
clock: func() time.Time { return now },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -264,8 +267,8 @@ func TestIssue(t *testing.T) {
|
|||||||
name: "signing error",
|
name: "signing error",
|
||||||
ca: CA{
|
ca: CA{
|
||||||
env: env{
|
env: env{
|
||||||
serialRNG: strings.NewReader(strings.Repeat("x", 64)),
|
serialRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
keygenRNG: strings.NewReader(strings.Repeat("x", 64)),
|
keygenRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
clock: func() time.Time { return now },
|
clock: func() time.Time { return now },
|
||||||
},
|
},
|
||||||
caCertBytes: realCA.caCertBytes,
|
caCertBytes: realCA.caCertBytes,
|
||||||
@ -277,11 +280,11 @@ func TestIssue(t *testing.T) {
|
|||||||
wantErr: "could not sign certificate: some signer error",
|
wantErr: "could not sign certificate: some signer error",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "parse certificate error",
|
||||||
ca: CA{
|
ca: CA{
|
||||||
env: env{
|
env: env{
|
||||||
serialRNG: strings.NewReader(strings.Repeat("x", 64)),
|
serialRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
keygenRNG: strings.NewReader(strings.Repeat("x", 64)),
|
keygenRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
clock: func() time.Time { return now },
|
clock: func() time.Time { return now },
|
||||||
parseCert: func(_ []byte) (*x509.Certificate, error) {
|
parseCert: func(_ []byte) (*x509.Certificate, error) {
|
||||||
return nil, fmt.Errorf("some parse certificate error")
|
return nil, fmt.Errorf("some parse certificate error")
|
||||||
@ -296,8 +299,8 @@ func TestIssue(t *testing.T) {
|
|||||||
name: "success",
|
name: "success",
|
||||||
ca: CA{
|
ca: CA{
|
||||||
env: env{
|
env: env{
|
||||||
serialRNG: strings.NewReader(strings.Repeat("x", 64)),
|
serialRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
keygenRNG: strings.NewReader(strings.Repeat("x", 64)),
|
keygenRNG: strings.NewReader(strings.Repeat("x", numRandBytes)),
|
||||||
clock: func() time.Time { return now },
|
clock: func() time.Time { return now },
|
||||||
parseCert: x509.ParseCertificate,
|
parseCert: x509.ParseCertificate,
|
||||||
},
|
},
|
||||||
@ -309,28 +312,26 @@ func TestIssue(t *testing.T) {
|
|||||||
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) {
|
||||||
got, err := tt.ca.Issue(pkix.Name{CommonName: "Test Server"}, []string{"example.com"}, []net.IP{net.IPv4(1, 2, 3, 4)}, 10*time.Minute)
|
got, err := tt.ca.IssueServerCert([]string{"example.com"}, []net.IP{net.IPv4(1, 2, 3, 4)}, 10*time.Minute)
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.EqualError(t, err, tt.wantErr)
|
require.EqualError(t, err, tt.wantErr)
|
||||||
require.Nil(t, got)
|
require.Nil(t, got)
|
||||||
return
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
}
|
||||||
|
got, err = tt.ca.IssueClientCert("test-user", []string{"group1", "group2"}, 10*time.Minute)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
require.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, got)
|
||||||
}
|
}
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, got)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssuePEM(t *testing.T) {
|
|
||||||
realCA, err := loadFromFiles(t, "./testdata/test.crt", "./testdata/test.key")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
certPEM, keyPEM, err := realCA.IssuePEM(pkix.Name{CommonName: "Test Server"}, []string{"example.com"}, nil, 10*time.Minute)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEmpty(t, certPEM)
|
|
||||||
require.NotEmpty(t, keyPEM)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToPEM(t *testing.T) {
|
func TestToPEM(t *testing.T) {
|
||||||
realCert, err := tls.LoadX509KeyPair("./testdata/test.crt", "./testdata/test.key")
|
realCert, err := tls.LoadX509KeyPair("./testdata/test.crt", "./testdata/test.key")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -358,3 +359,90 @@ func TestToPEM(t *testing.T) {
|
|||||||
require.NotEmpty(t, keyPEM)
|
require.NotEmpty(t, keyPEM)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssueMethods(t *testing.T) {
|
||||||
|
// One CA can be used to issue both kinds of certs.
|
||||||
|
ca, err := New("Test CA", time.Hour)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ttl := 121 * time.Hour
|
||||||
|
|
||||||
|
t.Run("client certs", func(t *testing.T) {
|
||||||
|
user := "test-username"
|
||||||
|
groups := []string{"group1", "group2"}
|
||||||
|
|
||||||
|
clientCert, err := ca.IssueClientCert(user, groups, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
certPEM, keyPEM, err := ToPEM(clientCert)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateClientCert(t, ca.Bundle(), certPEM, keyPEM, user, groups, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueClientCertPEM(user, groups, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateClientCert(t, ca.Bundle(), certPEM, keyPEM, user, groups, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueClientCertPEM(user, nil, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateClientCert(t, ca.Bundle(), certPEM, keyPEM, user, nil, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueClientCertPEM(user, []string{}, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateClientCert(t, ca.Bundle(), certPEM, keyPEM, user, nil, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueClientCertPEM("", []string{}, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateClientCert(t, ca.Bundle(), certPEM, keyPEM, "", nil, ttl)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("server certs", func(t *testing.T) {
|
||||||
|
dnsNames := []string{"example.com", "pinniped.dev"}
|
||||||
|
ips := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("1.2.3.4")}
|
||||||
|
|
||||||
|
serverCert, err := ca.IssueServerCert(dnsNames, ips, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
certPEM, keyPEM, err := ToPEM(serverCert)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateServerCert(t, ca.Bundle(), certPEM, keyPEM, dnsNames, ips, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueServerCertPEM(dnsNames, ips, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateServerCert(t, ca.Bundle(), certPEM, keyPEM, dnsNames, ips, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueServerCertPEM(nil, ips, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateServerCert(t, ca.Bundle(), certPEM, keyPEM, nil, ips, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueServerCertPEM(dnsNames, nil, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateServerCert(t, ca.Bundle(), certPEM, keyPEM, dnsNames, nil, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueServerCertPEM([]string{}, ips, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateServerCert(t, ca.Bundle(), certPEM, keyPEM, nil, ips, ttl)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err = ca.IssueServerCertPEM(dnsNames, []net.IP{}, ttl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validateServerCert(t, ca.Bundle(), certPEM, keyPEM, dnsNames, nil, ttl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateClientCert(t *testing.T, caBundle []byte, certPEM []byte, keyPEM []byte, expectedUser string, expectedGroups []string, expectedTTL time.Duration) {
|
||||||
|
const fudgeFactor = 10 * time.Second
|
||||||
|
v := testutil.ValidateClientCertificate(t, string(caBundle), string(certPEM))
|
||||||
|
v.RequireLifetime(time.Now(), time.Now().Add(expectedTTL), certBackdate+fudgeFactor)
|
||||||
|
v.RequireMatchesPrivateKey(string(keyPEM))
|
||||||
|
v.RequireCommonName(expectedUser)
|
||||||
|
v.RequireOrganizations(expectedGroups)
|
||||||
|
v.RequireEmptyDNSNames()
|
||||||
|
v.RequireEmptyIPs()
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateServerCert(t *testing.T, caBundle []byte, certPEM []byte, keyPEM []byte, expectedDNSNames []string, expectedIPs []net.IP, expectedTTL time.Duration) {
|
||||||
|
const fudgeFactor = 10 * time.Second
|
||||||
|
v := testutil.ValidateServerCertificate(t, string(caBundle), string(certPEM))
|
||||||
|
v.RequireLifetime(time.Now(), time.Now().Add(expectedTTL), certBackdate+fudgeFactor)
|
||||||
|
v.RequireMatchesPrivateKey(string(keyPEM))
|
||||||
|
v.RequireCommonName("")
|
||||||
|
v.RequireDNSNames(expectedDNSNames)
|
||||||
|
v.RequireIPs(expectedIPs)
|
||||||
|
}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
package dynamiccertauthority
|
package dynamiccertauthority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509/pkix"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||||
@ -27,14 +26,14 @@ func New(provider dynamiccertificates.CertKeyContentProvider) *CA {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssuePEM issues a new server certificate for the given identity and duration, returning it as a
|
// IssueClientCertPEM issues a new client certificate for the given identity and duration, returning it as a
|
||||||
// pair of PEM-formatted byte slices for the certificate and private key.
|
// pair of PEM-formatted byte slices for the certificate and private key.
|
||||||
func (c *CA) IssuePEM(subject pkix.Name, dnsNames []string, ttl time.Duration) ([]byte, []byte, error) {
|
func (c *CA) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
caCrtPEM, caKeyPEM := c.provider.CurrentCertKeyContent()
|
caCrtPEM, caKeyPEM := c.provider.CurrentCertKeyContent()
|
||||||
ca, err := certauthority.Load(string(caCrtPEM), string(caKeyPEM))
|
ca, err := certauthority.Load(string(caCrtPEM), string(caKeyPEM))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ca.IssuePEM(subject, dnsNames, nil, ttl)
|
return ca.IssueClientCertPEM(username, groups, ttl)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package dynamiccertauthority
|
package dynamiccertauthority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509/pkix"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -106,10 +105,9 @@ func TestCAIssuePEM(t *testing.T) {
|
|||||||
require.NotEmpty(t, keyPEM)
|
require.NotEmpty(t, keyPEM)
|
||||||
|
|
||||||
caCrtPEM, _ := provider.CurrentCertKeyContent()
|
caCrtPEM, _ := provider.CurrentCertKeyContent()
|
||||||
crtAssertions := testutil.ValidateCertificate(t, string(caCrtPEM), string(crtPEM))
|
crtAssertions := testutil.ValidateClientCertificate(t, string(caCrtPEM), string(crtPEM))
|
||||||
crtAssertions.RequireCommonName("some-common-name")
|
crtAssertions.RequireCommonName("some-username")
|
||||||
crtAssertions.RequireDNSName("some-dns-name")
|
crtAssertions.RequireOrganizations([]string{"some-group1", "some-group2"})
|
||||||
crtAssertions.RequireDNSName("some-other-dns-name")
|
|
||||||
crtAssertions.RequireLifetime(time.Now(), time.Now().Add(time.Hour*24), time.Minute*10)
|
crtAssertions.RequireLifetime(time.Now(), time.Now().Add(time.Hour*24), time.Minute*10)
|
||||||
crtAssertions.RequireMatchesPrivateKey(string(keyPEM))
|
crtAssertions.RequireMatchesPrivateKey(string(keyPEM))
|
||||||
}
|
}
|
||||||
@ -126,11 +124,5 @@ func issuePEM(provider dynamiccert.Provider, ca *CA, caCrt, caKey []byte) ([]byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// otherwise check to see if their is an issuing error
|
// otherwise check to see if their is an issuing error
|
||||||
return ca.IssuePEM(
|
return ca.IssueClientCertPEM("some-username", []string{"some-group1", "some-group2"}, time.Hour*24)
|
||||||
pkix.Name{
|
|
||||||
CommonName: "some-common-name",
|
|
||||||
},
|
|
||||||
[]string{"some-dns-name", "some-other-dns-name"},
|
|
||||||
time.Hour*24,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ type Config struct {
|
|||||||
|
|
||||||
type ExtraConfig struct {
|
type ExtraConfig struct {
|
||||||
Authenticator credentialrequest.TokenCredentialRequestAuthenticator
|
Authenticator credentialrequest.TokenCredentialRequestAuthenticator
|
||||||
Issuer issuer.CertIssuer
|
Issuer issuer.ClientCertIssuer
|
||||||
StartControllersPostStartHook func(ctx context.Context)
|
StartControllersPostStartHook func(ctx context.Context)
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
NegotiatedSerializer runtime.NegotiatedSerializer
|
NegotiatedSerializer runtime.NegotiatedSerializer
|
||||||
|
@ -5,7 +5,6 @@ package impersonator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -36,7 +35,7 @@ import (
|
|||||||
func TestImpersonator(t *testing.T) {
|
func TestImpersonator(t *testing.T) {
|
||||||
const port = 9444
|
const port = 9444
|
||||||
|
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: "ca"}, time.Hour)
|
ca, err := certauthority.New("ca", time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
caKey, err := ca.PrivateKeyToPEM()
|
caKey, err := ca.PrivateKeyToPEM()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -44,13 +43,13 @@ func TestImpersonator(t *testing.T) {
|
|||||||
err = caContent.SetCertKeyContent(ca.Bundle(), caKey)
|
err = caContent.SetCertKeyContent(ca.Bundle(), caKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cert, key, err := ca.IssuePEM(pkix.Name{}, nil, []net.IP{net.ParseIP("127.0.0.1")}, time.Hour)
|
cert, key, err := ca.IssueServerCertPEM(nil, []net.IP{net.ParseIP("127.0.0.1")}, time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
certKeyContent := dynamiccert.New("cert-key")
|
certKeyContent := dynamiccert.New("cert-key")
|
||||||
err = certKeyContent.SetCertKeyContent(cert, key)
|
err = certKeyContent.SetCertKeyContent(cert, key)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
unrelatedCA, err := certauthority.New(pkix.Name{CommonName: "ca"}, time.Hour)
|
unrelatedCA, err := certauthority.New("ca", time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Punch out just enough stuff to make New actually run without error.
|
// Punch out just enough stuff to make New actually run without error.
|
||||||
@ -486,9 +485,7 @@ type clientCert struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newClientCert(t *testing.T, ca *certauthority.CA, username string, groups []string) *clientCert {
|
func newClientCert(t *testing.T, ca *certauthority.CA, username string, groups []string) *clientCert {
|
||||||
certPEM, keyPEM, err := ca.IssuePEM(
|
certPEM, keyPEM, err := ca.IssueClientCertPEM(username, groups, time.Hour)
|
||||||
pkix.Name{CommonName: username, Organization: groups}, nil, nil, time.Hour,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return &clientCert{
|
return &clientCert{
|
||||||
certPEM: certPEM,
|
certPEM: certPEM,
|
||||||
|
@ -150,7 +150,7 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
return fmt.Errorf("could not prepare controllers: %w", err)
|
return fmt.Errorf("could not prepare controllers: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certIssuer := issuer.CertIssuers{
|
certIssuer := issuer.ClientCertIssuers{
|
||||||
dynamiccertauthority.New(dynamicSigningCertProvider), // attempt to use the real Kube CA if possible
|
dynamiccertauthority.New(dynamicSigningCertProvider), // attempt to use the real Kube CA if possible
|
||||||
dynamiccertauthority.New(impersonationProxySigningCertProvider), // fallback to our internal CA if we need to
|
dynamiccertauthority.New(impersonationProxySigningCertProvider), // fallback to our internal CA if we need to
|
||||||
}
|
}
|
||||||
@ -184,7 +184,7 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
func getAggregatedAPIServerConfig(
|
func getAggregatedAPIServerConfig(
|
||||||
dynamicCertProvider dynamiccert.Provider,
|
dynamicCertProvider dynamiccert.Provider,
|
||||||
authenticator credentialrequest.TokenCredentialRequestAuthenticator,
|
authenticator credentialrequest.TokenCredentialRequestAuthenticator,
|
||||||
issuer issuer.CertIssuer,
|
issuer issuer.ClientCertIssuer,
|
||||||
startControllersPostStartHook func(context.Context),
|
startControllersPostStartHook func(context.Context),
|
||||||
apiGroupSuffix string,
|
apiGroupSuffix string,
|
||||||
scheme *runtime.Scheme,
|
scheme *runtime.Scheme,
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
package apicerts
|
package apicerts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -94,7 +93,7 @@ func (c *certsManagerController) Sync(ctx controllerlib.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a CA.
|
// Create a CA.
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: c.generatedCACommonName}, c.certDuration)
|
ca, err := certauthority.New(c.generatedCACommonName, c.certDuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not initialize CA: %w", err)
|
return fmt.Errorf("could not initialize CA: %w", err)
|
||||||
}
|
}
|
||||||
@ -120,12 +119,7 @@ func (c *certsManagerController) Sync(ctx controllerlib.Context) error {
|
|||||||
// Using the CA from above, create a TLS server cert if we have service name.
|
// Using the CA from above, create a TLS server cert if we have service name.
|
||||||
if len(c.serviceNameForGeneratedCertCommonName) != 0 {
|
if len(c.serviceNameForGeneratedCertCommonName) != 0 {
|
||||||
serviceEndpoint := c.serviceNameForGeneratedCertCommonName + "." + c.namespace + ".svc"
|
serviceEndpoint := c.serviceNameForGeneratedCertCommonName + "." + c.namespace + ".svc"
|
||||||
tlsCert, err := ca.Issue(
|
tlsCert, err := ca.IssueServerCert([]string{serviceEndpoint}, nil, c.certDuration)
|
||||||
pkix.Name{CommonName: serviceEndpoint},
|
|
||||||
[]string{serviceEndpoint},
|
|
||||||
nil,
|
|
||||||
c.certDuration,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not issue serving certificate: %w", err)
|
return fmt.Errorf("could not issue serving certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -218,12 +218,12 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
r.NotEmpty(actualCertChain)
|
r.NotEmpty(actualCertChain)
|
||||||
r.Len(actualSecret.StringData, 4)
|
r.Len(actualSecret.StringData, 4)
|
||||||
|
|
||||||
validCACert := testutil.ValidateCertificate(t, actualCACert, actualCACert)
|
validCACert := testutil.ValidateServerCertificate(t, actualCACert, actualCACert)
|
||||||
validCACert.RequireMatchesPrivateKey(actualCAPrivateKey)
|
validCACert.RequireMatchesPrivateKey(actualCAPrivateKey)
|
||||||
validCACert.RequireLifetime(time.Now(), time.Now().Add(certDuration), 6*time.Minute)
|
validCACert.RequireLifetime(time.Now(), time.Now().Add(certDuration), 6*time.Minute)
|
||||||
|
|
||||||
// Validate the created cert using the CA, and also validate the cert's hostname
|
// Validate the created cert using the CA, and also validate the cert's hostname
|
||||||
validCert := testutil.ValidateCertificate(t, actualCACert, actualCertChain)
|
validCert := testutil.ValidateServerCertificate(t, actualCACert, actualCertChain)
|
||||||
validCert.RequireDNSName("pinniped-api." + installedInNamespace + ".svc")
|
validCert.RequireDNSName("pinniped-api." + installedInNamespace + ".svc")
|
||||||
validCert.RequireLifetime(time.Now(), time.Now().Add(certDuration), 6*time.Minute)
|
validCert.RequireLifetime(time.Now(), time.Now().Add(certDuration), 6*time.Minute)
|
||||||
validCert.RequireMatchesPrivateKey(actualPrivateKey)
|
validCert.RequireMatchesPrivateKey(actualPrivateKey)
|
||||||
@ -252,7 +252,7 @@ func TestManagerControllerSync(t *testing.T) {
|
|||||||
r.NotEmpty(actualCAPrivateKey)
|
r.NotEmpty(actualCAPrivateKey)
|
||||||
r.Len(actualSecret.StringData, 2)
|
r.Len(actualSecret.StringData, 2)
|
||||||
|
|
||||||
validCACert := testutil.ValidateCertificate(t, actualCACert, actualCACert)
|
validCACert := testutil.ValidateServerCertificate(t, actualCACert, actualCACert)
|
||||||
validCACert.RequireMatchesPrivateKey(actualCAPrivateKey)
|
validCACert.RequireMatchesPrivateKey(actualCAPrivateKey)
|
||||||
validCACert.RequireLifetime(time.Now(), time.Now().Add(certDuration), 6*time.Minute)
|
validCACert.RequireLifetime(time.Now(), time.Now().Add(certDuration), 6*time.Minute)
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -40,13 +39,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
impersonationProxyPort = 8444
|
impersonationProxyPort = 8444
|
||||||
defaultHTTPSPort = 443
|
defaultHTTPSPort = 443
|
||||||
oneHundredYears = 100 * 365 * 24 * time.Hour
|
approximatelyOneHundredYears = 100 * 365 * 24 * time.Hour
|
||||||
caCommonName = "Pinniped Impersonation Proxy CA"
|
caCommonName = "Pinniped Impersonation Proxy CA"
|
||||||
caCrtKey = "ca.crt"
|
caCrtKey = "ca.crt"
|
||||||
caKeyKey = "ca.key"
|
caKeyKey = "ca.key"
|
||||||
appLabelKey = "app"
|
appLabelKey = "app"
|
||||||
)
|
)
|
||||||
|
|
||||||
type impersonatorConfigController struct {
|
type impersonatorConfigController struct {
|
||||||
@ -622,7 +621,7 @@ func (c *impersonatorConfigController) ensureCASecretIsCreated(ctx context.Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *impersonatorConfigController) createCASecret(ctx context.Context) (*certauthority.CA, error) {
|
func (c *impersonatorConfigController) createCASecret(ctx context.Context) (*certauthority.CA, error) {
|
||||||
impersonationCA, err := certauthority.New(pkix.Name{CommonName: caCommonName}, oneHundredYears)
|
impersonationCA, err := certauthority.New(caCommonName, approximatelyOneHundredYears)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not create impersonation CA: %w", err)
|
return nil, fmt.Errorf("could not create impersonation CA: %w", err)
|
||||||
}
|
}
|
||||||
@ -716,7 +715,7 @@ func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, c
|
|||||||
ips = []net.IP{ip}
|
ips = []net.IP{ip}
|
||||||
}
|
}
|
||||||
|
|
||||||
impersonationCert, err := ca.Issue(pkix.Name{}, hostnames, ips, oneHundredYears)
|
impersonationCert, err := ca.IssueServerCert(hostnames, ips, approximatelyOneHundredYears)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not create impersonation cert: %w", err)
|
return nil, fmt.Errorf("could not create impersonation cert: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
@ -352,7 +351,10 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
roots := x509.NewCertPool()
|
roots := x509.NewCertPool()
|
||||||
require.True(t, roots.AppendCertsFromPEM(currentClientCertCA))
|
require.True(t, roots.AppendCertsFromPEM(currentClientCertCA))
|
||||||
opts := x509.VerifyOptions{Roots: roots}
|
opts := x509.VerifyOptions{
|
||||||
|
Roots: roots,
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||||
|
}
|
||||||
_, err = parsed.Verify(opts)
|
_, err = parsed.Verify(opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return nil
|
return nil
|
||||||
@ -594,7 +596,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newCA = func() *certauthority.CA {
|
var newCA = func() *certauthority.CA {
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: "test CA"}, 24*time.Hour)
|
ca, err := certauthority.New("test CA", 24*time.Hour)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
return ca
|
return ca
|
||||||
}
|
}
|
||||||
@ -609,7 +611,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newTLSCertSecretData = func(ca *certauthority.CA, dnsNames []string, ip string) map[string][]byte {
|
var newTLSCertSecretData = func(ca *certauthority.CA, dnsNames []string, ip string) map[string][]byte {
|
||||||
impersonationCert, err := ca.Issue(pkix.Name{}, dnsNames, []net.IP{net.ParseIP(ip)}, 24*time.Hour)
|
impersonationCert, err := ca.IssueServerCert(dnsNames, []net.IP{net.ParseIP(ip)}, 24*time.Hour)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
certPEM, keyPEM, err := certauthority.ToPEM(impersonationCert)
|
certPEM, keyPEM, err := certauthority.ToPEM(impersonationCert)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
@ -939,7 +941,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
createdKeyPEM := createdSecret.Data[corev1.TLSPrivateKeyKey]
|
createdKeyPEM := createdSecret.Data[corev1.TLSPrivateKeyKey]
|
||||||
r.NotNil(createdKeyPEM)
|
r.NotNil(createdKeyPEM)
|
||||||
r.NotNil(createdCertPEM)
|
r.NotNil(createdCertPEM)
|
||||||
validCert := testutil.ValidateCertificate(t, string(caCert), string(createdCertPEM))
|
validCert := testutil.ValidateServerCertificate(t, string(caCert), string(createdCertPEM))
|
||||||
validCert.RequireMatchesPrivateKey(string(createdKeyPEM))
|
validCert.RequireMatchesPrivateKey(string(createdKeyPEM))
|
||||||
validCert.RequireLifetime(time.Now().Add(-10*time.Second), time.Now().Add(100*time.Hour*24*365), 10*time.Second)
|
validCert.RequireLifetime(time.Now().Add(-10*time.Second), time.Now().Add(100*time.Hour*24*365), 10*time.Second)
|
||||||
}
|
}
|
||||||
@ -980,7 +982,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
signingCAKeyPEM, err = ca.PrivateKeyToPEM()
|
signingCAKeyPEM, err = ca.PrivateKeyToPEM()
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
signingCASecret = newSigningKeySecret(caSignerName, signingCACertPEM, signingCAKeyPEM)
|
signingCASecret = newSigningKeySecret(caSignerName, signingCACertPEM, signingCAKeyPEM)
|
||||||
validClientCert, err = ca.Issue(pkix.Name{}, nil, nil, time.Hour)
|
validClientCert, err = ca.IssueClientCert("username", nil, time.Hour)
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
package issuer
|
package issuer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509/pkix"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/errors"
|
"k8s.io/apimachinery/pkg/util/errors"
|
||||||
@ -14,19 +13,19 @@ import (
|
|||||||
|
|
||||||
const defaultCertIssuerErr = constable.Error("failed to issue cert")
|
const defaultCertIssuerErr = constable.Error("failed to issue cert")
|
||||||
|
|
||||||
type CertIssuer interface {
|
type ClientCertIssuer interface {
|
||||||
IssuePEM(subject pkix.Name, dnsNames []string, ttl time.Duration) (certPEM, keyPEM []byte, err error)
|
IssueClientCertPEM(username string, groups []string, ttl time.Duration) (certPEM, keyPEM []byte, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ CertIssuer = CertIssuers{}
|
var _ ClientCertIssuer = ClientCertIssuers{}
|
||||||
|
|
||||||
type CertIssuers []CertIssuer
|
type ClientCertIssuers []ClientCertIssuer
|
||||||
|
|
||||||
func (c CertIssuers) IssuePEM(subject pkix.Name, dnsNames []string, ttl time.Duration) ([]byte, []byte, error) {
|
func (c ClientCertIssuers) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
for _, issuer := range c {
|
for _, issuer := range c {
|
||||||
certPEM, keyPEM, err := issuer.IssuePEM(subject, dnsNames, ttl)
|
certPEM, keyPEM, err := issuer.IssueClientCertPEM(username, groups, ttl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
continue
|
continue
|
||||||
|
@ -3,61 +3,20 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: go.pinniped.dev/internal/registry/credentialrequest (interfaces: CertIssuer,TokenCredentialRequestAuthenticator)
|
// Source: go.pinniped.dev/internal/registry/credentialrequest (interfaces: TokenCredentialRequestAuthenticator)
|
||||||
|
|
||||||
// Package credentialrequestmocks is a generated GoMock package.
|
// Package credentialrequestmocks is a generated GoMock package.
|
||||||
package credentialrequestmocks
|
package credentialrequestmocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
pkix "crypto/x509/pkix"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
time "time"
|
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
login "go.pinniped.dev/generated/latest/apis/concierge/login"
|
login "go.pinniped.dev/generated/latest/apis/concierge/login"
|
||||||
user "k8s.io/apiserver/pkg/authentication/user"
|
user "k8s.io/apiserver/pkg/authentication/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockCertIssuer is a mock of CertIssuer interface.
|
|
||||||
type MockCertIssuer struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockCertIssuerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockCertIssuerMockRecorder is the mock recorder for MockCertIssuer.
|
|
||||||
type MockCertIssuerMockRecorder struct {
|
|
||||||
mock *MockCertIssuer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockCertIssuer creates a new mock instance.
|
|
||||||
func NewMockCertIssuer(ctrl *gomock.Controller) *MockCertIssuer {
|
|
||||||
mock := &MockCertIssuer{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockCertIssuerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockCertIssuer) EXPECT() *MockCertIssuerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssuePEM mocks base method.
|
|
||||||
func (m *MockCertIssuer) IssuePEM(arg0 pkix.Name, arg1 []string, arg2 time.Duration) ([]byte, []byte, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "IssuePEM", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].([]byte)
|
|
||||||
ret1, _ := ret[1].([]byte)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssuePEM indicates an expected call of IssuePEM.
|
|
||||||
func (mr *MockCertIssuerMockRecorder) IssuePEM(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssuePEM", reflect.TypeOf((*MockCertIssuer)(nil).IssuePEM), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockTokenCredentialRequestAuthenticator is a mock of TokenCredentialRequestAuthenticator interface.
|
// MockTokenCredentialRequestAuthenticator is a mock of TokenCredentialRequestAuthenticator interface.
|
||||||
type MockTokenCredentialRequestAuthenticator struct {
|
type MockTokenCredentialRequestAuthenticator struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package credentialrequestmocks
|
package credentialrequestmocks
|
||||||
|
|
||||||
//go:generate go run -v github.com/golang/mock/mockgen -destination=credentialrequestmocks.go -package=credentialrequestmocks -copyright_file=../../../hack/header.txt go.pinniped.dev/internal/registry/credentialrequest CertIssuer,TokenCredentialRequestAuthenticator
|
//go:generate go run -v github.com/golang/mock/mockgen -destination=credentialrequestmocks.go -package=credentialrequestmocks -copyright_file=../../../hack/header.txt go.pinniped.dev/internal/registry/credentialrequest TokenCredentialRequestAuthenticator
|
||||||
|
6
internal/mocks/issuermocks/generate.go
Normal file
6
internal/mocks/issuermocks/generate.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package issuermocks
|
||||||
|
|
||||||
|
//go:generate go run -v github.com/golang/mock/mockgen -destination=issuermocks.go -package=issuermocks -copyright_file=../../../hack/header.txt go.pinniped.dev/internal/issuer ClientCertIssuer
|
55
internal/mocks/issuermocks/issuermocks.go
Normal file
55
internal/mocks/issuermocks/issuermocks.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: go.pinniped.dev/internal/issuer (interfaces: ClientCertIssuer)
|
||||||
|
|
||||||
|
// Package issuermocks is a generated GoMock package.
|
||||||
|
package issuermocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
time "time"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockClientCertIssuer is a mock of ClientCertIssuer interface.
|
||||||
|
type MockClientCertIssuer struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockClientCertIssuerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockClientCertIssuerMockRecorder is the mock recorder for MockClientCertIssuer.
|
||||||
|
type MockClientCertIssuerMockRecorder struct {
|
||||||
|
mock *MockClientCertIssuer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockClientCertIssuer creates a new mock instance.
|
||||||
|
func NewMockClientCertIssuer(ctrl *gomock.Controller) *MockClientCertIssuer {
|
||||||
|
mock := &MockClientCertIssuer{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockClientCertIssuerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockClientCertIssuer) EXPECT() *MockClientCertIssuerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueClientCertPEM mocks base method.
|
||||||
|
func (m *MockClientCertIssuer) IssueClientCertPEM(arg0 string, arg1 []string, arg2 time.Duration) ([]byte, []byte, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "IssueClientCertPEM", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].([]byte)
|
||||||
|
ret1, _ := ret[1].([]byte)
|
||||||
|
ret2, _ := ret[2].(error)
|
||||||
|
return ret0, ret1, ret2
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueClientCertPEM indicates an expected call of IssueClientCertPEM.
|
||||||
|
func (mr *MockClientCertIssuerMockRecorder) IssueClientCertPEM(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueClientCertPEM", reflect.TypeOf((*MockClientCertIssuer)(nil).IssueClientCertPEM), arg0, arg1, arg2)
|
||||||
|
}
|
@ -6,7 +6,6 @@ package credentialrequest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ type TokenCredentialRequestAuthenticator interface {
|
|||||||
AuthenticateTokenCredentialRequest(ctx context.Context, req *loginapi.TokenCredentialRequest) (user.Info, error)
|
AuthenticateTokenCredentialRequest(ctx context.Context, req *loginapi.TokenCredentialRequest) (user.Info, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewREST(authenticator TokenCredentialRequestAuthenticator, issuer issuer.CertIssuer, resource schema.GroupResource) *REST {
|
func NewREST(authenticator TokenCredentialRequestAuthenticator, issuer issuer.ClientCertIssuer, resource schema.GroupResource) *REST {
|
||||||
return &REST{
|
return &REST{
|
||||||
authenticator: authenticator,
|
authenticator: authenticator,
|
||||||
issuer: issuer,
|
issuer: issuer,
|
||||||
@ -42,7 +41,7 @@ func NewREST(authenticator TokenCredentialRequestAuthenticator, issuer issuer.Ce
|
|||||||
|
|
||||||
type REST struct {
|
type REST struct {
|
||||||
authenticator TokenCredentialRequestAuthenticator
|
authenticator TokenCredentialRequestAuthenticator
|
||||||
issuer issuer.CertIssuer
|
issuer issuer.ClientCertIssuer
|
||||||
tableConvertor rest.TableConvertor
|
tableConvertor rest.TableConvertor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,30 +96,23 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := r.authenticator.AuthenticateTokenCredentialRequest(ctx, credentialRequest)
|
userInfo, err := r.authenticator.AuthenticateTokenCredentialRequest(ctx, credentialRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
traceFailureWithError(t, "token authentication", err)
|
traceFailureWithError(t, "token authentication", err)
|
||||||
return failureResponse(), nil
|
return failureResponse(), nil
|
||||||
}
|
}
|
||||||
if user == nil || user.GetName() == "" {
|
if userInfo == nil || userInfo.GetName() == "" {
|
||||||
traceSuccess(t, user, false)
|
traceSuccess(t, userInfo, false)
|
||||||
return failureResponse(), nil
|
return failureResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
certPEM, keyPEM, err := r.issuer.IssuePEM(
|
certPEM, keyPEM, err := r.issuer.IssueClientCertPEM(userInfo.GetName(), userInfo.GetGroups(), clientCertificateTTL)
|
||||||
pkix.Name{
|
|
||||||
CommonName: user.GetName(),
|
|
||||||
Organization: user.GetGroups(),
|
|
||||||
},
|
|
||||||
[]string{},
|
|
||||||
clientCertificateTTL,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
traceFailureWithError(t, "cert issuer", err)
|
traceFailureWithError(t, "cert issuer", err)
|
||||||
return failureResponse(), nil
|
return failureResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
traceSuccess(t, user, true)
|
traceSuccess(t, userInfo, true)
|
||||||
|
|
||||||
return &loginapi.TokenCredentialRequest{
|
return &loginapi.TokenCredentialRequest{
|
||||||
Status: loginapi.TokenCredentialRequestStatus{
|
Status: loginapi.TokenCredentialRequestStatus{
|
||||||
|
@ -5,7 +5,6 @@ package credentialrequest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
@ -26,6 +25,7 @@ import (
|
|||||||
loginapi "go.pinniped.dev/generated/latest/apis/concierge/login"
|
loginapi "go.pinniped.dev/generated/latest/apis/concierge/login"
|
||||||
"go.pinniped.dev/internal/issuer"
|
"go.pinniped.dev/internal/issuer"
|
||||||
"go.pinniped.dev/internal/mocks/credentialrequestmocks"
|
"go.pinniped.dev/internal/mocks/credentialrequestmocks"
|
||||||
|
"go.pinniped.dev/internal/mocks/issuermocks"
|
||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,16 +89,14 @@ func TestCreate(t *testing.T) {
|
|||||||
Groups: []string{"test-group-1", "test-group-2"},
|
Groups: []string{"test-group-1", "test-group-2"},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
issuer := credentialrequestmocks.NewMockCertIssuer(ctrl)
|
clientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
issuer.EXPECT().IssuePEM(
|
clientCertIssuer.EXPECT().IssueClientCertPEM(
|
||||||
pkix.Name{
|
"test-user",
|
||||||
CommonName: "test-user",
|
[]string{"test-group-1", "test-group-2"},
|
||||||
Organization: []string{"test-group-1", "test-group-2"}},
|
|
||||||
[]string{},
|
|
||||||
5*time.Minute,
|
5*time.Minute,
|
||||||
).Return([]byte("test-cert"), []byte("test-key"), nil)
|
).Return([]byte("test-cert"), []byte("test-key"), nil)
|
||||||
|
|
||||||
storage := NewREST(requestAuthenticator, issuer, schema.GroupResource{})
|
storage := NewREST(requestAuthenticator, clientCertIssuer, schema.GroupResource{})
|
||||||
|
|
||||||
response, err := callCreate(context.Background(), storage, req)
|
response, err := callCreate(context.Background(), storage, req)
|
||||||
|
|
||||||
@ -132,12 +130,12 @@ func TestCreate(t *testing.T) {
|
|||||||
Groups: []string{"test-group-1", "test-group-2"},
|
Groups: []string{"test-group-1", "test-group-2"},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
issuer := credentialrequestmocks.NewMockCertIssuer(ctrl)
|
clientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
issuer.EXPECT().
|
clientCertIssuer.EXPECT().
|
||||||
IssuePEM(gomock.Any(), gomock.Any(), gomock.Any()).
|
IssueClientCertPEM(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||||
Return(nil, nil, fmt.Errorf("some certificate authority error"))
|
Return(nil, nil, fmt.Errorf("some certificate authority error"))
|
||||||
|
|
||||||
storage := NewREST(requestAuthenticator, issuer, schema.GroupResource{})
|
storage := NewREST(requestAuthenticator, clientCertIssuer, schema.GroupResource{})
|
||||||
|
|
||||||
response, err := callCreate(context.Background(), storage, req)
|
response, err := callCreate(context.Background(), storage, req)
|
||||||
requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response)
|
requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response)
|
||||||
@ -354,12 +352,12 @@ func requireSuccessfulResponseWithAuthenticationFailureMessage(t *testing.T, err
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func successfulIssuer(ctrl *gomock.Controller) issuer.CertIssuer {
|
func successfulIssuer(ctrl *gomock.Controller) issuer.ClientCertIssuer {
|
||||||
certIssuer := credentialrequestmocks.NewMockCertIssuer(ctrl)
|
clientCertIssuer := issuermocks.NewMockClientCertIssuer(ctrl)
|
||||||
certIssuer.EXPECT().
|
clientCertIssuer.EXPECT().
|
||||||
IssuePEM(gomock.Any(), gomock.Any(), gomock.Any()).
|
IssueClientCertPEM(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||||
Return([]byte("test-cert"), []byte("test-key"), nil)
|
Return([]byte("test-cert"), []byte("test-key"), nil)
|
||||||
return certIssuer
|
return clientCertIssuer
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringPtr(s string) *string {
|
func stringPtr(s string) *string {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package testutil
|
package testutil
|
||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -25,10 +26,18 @@ type ValidCert struct {
|
|||||||
parsed *x509.Certificate
|
parsed *x509.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateCertificate validates a certificate and provides an object for asserting properties of the certificate.
|
// ValidateServerCertificate validates a certificate and provides an object for asserting properties of the certificate.
|
||||||
func ValidateCertificate(t *testing.T, caPEM string, certPEM string) *ValidCert {
|
func ValidateServerCertificate(t *testing.T, caPEM string, certPEM string) *ValidCert {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
return validateCertificate(t, x509.ExtKeyUsageServerAuth, caPEM, certPEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateClientCertificate(t *testing.T, caPEM string, certPEM string) *ValidCert {
|
||||||
|
t.Helper()
|
||||||
|
return validateCertificate(t, x509.ExtKeyUsageClientAuth, caPEM, certPEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCertificate(t *testing.T, extKeyUsage x509.ExtKeyUsage, caPEM string, certPEM string) *ValidCert {
|
||||||
block, _ := pem.Decode([]byte(certPEM))
|
block, _ := pem.Decode([]byte(certPEM))
|
||||||
require.NotNil(t, block)
|
require.NotNil(t, block)
|
||||||
parsed, err := x509.ParseCertificate(block.Bytes)
|
parsed, err := x509.ParseCertificate(block.Bytes)
|
||||||
@ -37,7 +46,10 @@ func ValidateCertificate(t *testing.T, caPEM string, certPEM string) *ValidCert
|
|||||||
// Validate the created cert using the CA.
|
// Validate the created cert using the CA.
|
||||||
roots := x509.NewCertPool()
|
roots := x509.NewCertPool()
|
||||||
require.True(t, roots.AppendCertsFromPEM([]byte(caPEM)))
|
require.True(t, roots.AppendCertsFromPEM([]byte(caPEM)))
|
||||||
opts := x509.VerifyOptions{Roots: roots}
|
opts := x509.VerifyOptions{
|
||||||
|
Roots: roots,
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{extKeyUsage},
|
||||||
|
}
|
||||||
_, err = parsed.Verify(opts)
|
_, err = parsed.Verify(opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -61,6 +73,35 @@ func (v *ValidCert) RequireDNSName(expectDNSName string) {
|
|||||||
require.Contains(v.t, v.parsed.DNSNames, expectDNSName, "expected an explicit DNS SAN, not just Common Name")
|
require.Contains(v.t, v.parsed.DNSNames, expectDNSName, "expected an explicit DNS SAN, not just Common Name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *ValidCert) RequireDNSNames(names []string) {
|
||||||
|
v.t.Helper()
|
||||||
|
require.Equal(v.t, names, v.parsed.DNSNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ValidCert) RequireEmptyDNSNames() {
|
||||||
|
v.t.Helper()
|
||||||
|
require.Empty(v.t, v.parsed.DNSNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ValidCert) RequireIPs(ips []net.IP) {
|
||||||
|
v.t.Helper()
|
||||||
|
actualIPs := v.parsed.IPAddresses
|
||||||
|
actualIPsStrings := make([]string, len(actualIPs))
|
||||||
|
for i := range actualIPs {
|
||||||
|
actualIPsStrings[i] = actualIPs[i].String()
|
||||||
|
}
|
||||||
|
expectedIPsStrings := make([]string, len(ips))
|
||||||
|
for i := range ips {
|
||||||
|
expectedIPsStrings[i] = ips[i].String()
|
||||||
|
}
|
||||||
|
require.Equal(v.t, expectedIPsStrings, actualIPsStrings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ValidCert) RequireEmptyIPs() {
|
||||||
|
v.t.Helper()
|
||||||
|
require.Empty(v.t, v.parsed.IPAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
// RequireLifetime asserts that the lifetime of the certificate matches the expected timestamps.
|
// RequireLifetime asserts that the lifetime of the certificate matches the expected timestamps.
|
||||||
func (v *ValidCert) RequireLifetime(expectNotBefore time.Time, expectNotAfter time.Time, delta time.Duration) {
|
func (v *ValidCert) RequireLifetime(expectNotBefore time.Time, expectNotAfter time.Time, delta time.Duration) {
|
||||||
v.t.Helper()
|
v.t.Helper()
|
||||||
@ -81,6 +122,11 @@ func (v *ValidCert) RequireCommonName(commonName string) {
|
|||||||
require.Equal(v.t, commonName, v.parsed.Subject.CommonName)
|
require.Equal(v.t, commonName, v.parsed.Subject.CommonName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *ValidCert) RequireOrganizations(orgs []string) {
|
||||||
|
v.t.Helper()
|
||||||
|
require.Equal(v.t, orgs, v.parsed.Subject.Organization)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateCertificate creates a certificate with the provided time bounds, and returns the PEM
|
// CreateCertificate creates a certificate with the provided time bounds, and returns the PEM
|
||||||
// representation of the certificate and its private key. The returned certificate is capable of
|
// representation of the certificate and its private key. The returned certificate is capable of
|
||||||
// signing child certificates.
|
// signing child certificates.
|
||||||
|
@ -5,7 +5,6 @@ package conciergeclient
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -26,7 +25,7 @@ import (
|
|||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testCA, err := certauthority.New(pkix.Name{}, 1*time.Hour)
|
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -69,7 +68,7 @@ func TestE2EFullIntegration(t *testing.T) {
|
|||||||
|
|
||||||
// Generate a CA bundle with which to serve this provider.
|
// Generate a CA bundle with which to serve this provider.
|
||||||
t.Logf("generating test CA")
|
t.Logf("generating test CA")
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: "Downstream Test CA"}, 1*time.Hour)
|
ca, err := certauthority.New("Downstream Test CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Save that bundle plus the one that signs the upstream issuer, for test purposes.
|
// Save that bundle plus the one that signs the upstream issuer, for test purposes.
|
||||||
@ -80,12 +79,7 @@ func TestE2EFullIntegration(t *testing.T) {
|
|||||||
|
|
||||||
// Use the CA to issue a TLS server cert.
|
// Use the CA to issue a TLS server cert.
|
||||||
t.Logf("issuing test certificate")
|
t.Logf("issuing test certificate")
|
||||||
tlsCert, err := ca.Issue(
|
tlsCert, err := ca.IssueServerCert([]string{issuerURL.Hostname()}, nil, 1*time.Hour)
|
||||||
pkix.Name{CommonName: issuerURL.Hostname()},
|
|
||||||
[]string{issuerURL.Hostname()},
|
|
||||||
nil,
|
|
||||||
1*time.Hour,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
certPEM, keyPEM, err := certauthority.ToPEM(tlsCert)
|
certPEM, keyPEM, err := certauthority.ToPEM(tlsCert)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -288,11 +287,11 @@ func defaultTLSCertSecretName(env *library.TestEnv) string {
|
|||||||
|
|
||||||
func createTLSCertificateSecret(ctx context.Context, t *testing.T, ns string, hostname string, ips []net.IP, secretName string, kubeClient kubernetes.Interface) *certauthority.CA {
|
func createTLSCertificateSecret(ctx context.Context, t *testing.T, ns string, hostname string, ips []net.IP, secretName string, kubeClient kubernetes.Interface) *certauthority.CA {
|
||||||
// Create a CA.
|
// Create a CA.
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: "Acme Corp"}, 1000*time.Hour)
|
ca, err := certauthority.New("Acme Corp", 1000*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Using the CA, create a TLS server cert.
|
// Using the CA, create a TLS server cert.
|
||||||
tlsCert, err := ca.Issue(pkix.Name{CommonName: hostname}, []string{hostname}, ips, 1000*time.Hour)
|
tlsCert, err := ca.IssueServerCert([]string{hostname}, ips, 1000*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Write the serving cert to the SNI secret.
|
// Write the serving cert to the SNI secret.
|
||||||
|
@ -6,7 +6,6 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -58,7 +57,7 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
|
|
||||||
// Generate a CA bundle with which to serve this provider.
|
// Generate a CA bundle with which to serve this provider.
|
||||||
t.Logf("generating test CA")
|
t.Logf("generating test CA")
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: "Downstream Test CA"}, 1*time.Hour)
|
ca, err := certauthority.New("Downstream Test CA", 1*time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create an HTTP client that can reach the downstream discovery endpoint using the CA certs.
|
// Create an HTTP client that can reach the downstream discovery endpoint using the CA certs.
|
||||||
@ -85,12 +84,7 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
|
|
||||||
// Use the CA to issue a TLS server cert.
|
// Use the CA to issue a TLS server cert.
|
||||||
t.Logf("issuing test certificate")
|
t.Logf("issuing test certificate")
|
||||||
tlsCert, err := ca.Issue(
|
tlsCert, err := ca.IssueServerCert([]string{issuerURL.Hostname()}, nil, 1*time.Hour)
|
||||||
pkix.Name{CommonName: issuerURL.Hostname()},
|
|
||||||
[]string{issuerURL.Hostname()},
|
|
||||||
nil,
|
|
||||||
1*time.Hour,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
certPEM, keyPEM, err := certauthority.ToPEM(tlsCert)
|
certPEM, keyPEM, err := certauthority.ToPEM(tlsCert)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user