internal/certauthority/dynamiccertauthority: add new dynamic cert issuer
This thing is supposed to be used to help our CredentialRequest handler issue certs with a dynamic CA keypair. Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
parent
6c555f94e3
commit
406f2723ce
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package dynamiccertauthority implements a x509 certificate authority capable of issuing
|
||||||
|
// certificates from a dynamically updating CA keypair.
|
||||||
|
package dynamiccertauthority
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/certauthority"
|
||||||
|
"go.pinniped.dev/internal/dynamiccert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CA is a type capable of issuing certificates.
|
||||||
|
type CA struct {
|
||||||
|
provider dynamiccert.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new CA, ready to issue certs whenever the provided provider has a keypair to
|
||||||
|
// provide.
|
||||||
|
func New(provider dynamiccert.Provider) *CA {
|
||||||
|
return &CA{
|
||||||
|
provider: provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, ttl time.Duration) ([]byte, []byte, error) {
|
||||||
|
caCrtPEM, caKeyPEM := c.provider.CurrentCertKeyContent()
|
||||||
|
ca, err := certauthority.Load(string(caCrtPEM), string(caKeyPEM))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ca.IssuePEM(subject, dnsNames, ttl)
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package dynamiccertauthority
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/dynamiccert"
|
||||||
|
"go.pinniped.dev/internal/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCAIssuePEM(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
provider := dynamiccert.New()
|
||||||
|
ca := New(provider)
|
||||||
|
|
||||||
|
steps := []struct {
|
||||||
|
name string
|
||||||
|
caCrtPath, caKeyPath string
|
||||||
|
wantError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no cert+key",
|
||||||
|
wantError: "could not load CA: tls: failed to find any PEM data in certificate input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only cert",
|
||||||
|
caCrtPath: "testdata/ca-0.crt",
|
||||||
|
wantError: "could not load CA: tls: failed to find any PEM data in key input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only key",
|
||||||
|
caKeyPath: "testdata/ca-0.key",
|
||||||
|
wantError: "could not load CA: tls: failed to find any PEM data in certificate input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "new cert+key",
|
||||||
|
caCrtPath: "testdata/ca-0.crt",
|
||||||
|
caKeyPath: "testdata/ca-0.key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same cert+key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "another new cert+key",
|
||||||
|
caCrtPath: "testdata/ca-1.crt",
|
||||||
|
caKeyPath: "testdata/ca-1.key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad cert",
|
||||||
|
caCrtPath: "testdata/ca-bad.crt",
|
||||||
|
caKeyPath: "testdata/ca-0.key",
|
||||||
|
wantError: "could not load CA: tls: failed to find any PEM data in certificate input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad key",
|
||||||
|
caCrtPath: "testdata/ca-0.crt",
|
||||||
|
caKeyPath: "testdata/ca-bad.key",
|
||||||
|
wantError: "could not load CA: tls: failed to find any PEM data in key input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mismatch cert+key",
|
||||||
|
caCrtPath: "testdata/ca-0.crt",
|
||||||
|
caKeyPath: "testdata/ca-1.key",
|
||||||
|
wantError: "could not load CA: tls: private key does not match public key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good cert+key again",
|
||||||
|
caCrtPath: "testdata/ca-1.crt",
|
||||||
|
caKeyPath: "testdata/ca-1.key",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, step := range steps {
|
||||||
|
step := step
|
||||||
|
t.Run(step.name, func(t *testing.T) {
|
||||||
|
var caCrtPEM, caKeyPEM []byte
|
||||||
|
var err error
|
||||||
|
if step.caCrtPath != "" {
|
||||||
|
caCrtPEM, err = ioutil.ReadFile(step.caCrtPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if step.caKeyPath != "" {
|
||||||
|
caKeyPEM, err = ioutil.ReadFile(step.caKeyPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if step.caCrtPath != "" || step.caKeyPath != "" {
|
||||||
|
provider.Set(caCrtPEM, caKeyPEM)
|
||||||
|
} else {
|
||||||
|
caCrtPEM, _ = provider.CurrentCertKeyContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
crtPEM, keyPEM, err := ca.IssuePEM(
|
||||||
|
pkix.Name{
|
||||||
|
CommonName: "some-common-name",
|
||||||
|
},
|
||||||
|
[]string{"some-dns-name", "some-other-dns-name"},
|
||||||
|
time.Hour*24,
|
||||||
|
)
|
||||||
|
|
||||||
|
if step.wantError != "" {
|
||||||
|
require.EqualError(t, err, step.wantError)
|
||||||
|
require.Empty(t, crtPEM)
|
||||||
|
require.Empty(t, keyPEM)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, crtPEM)
|
||||||
|
require.NotEmpty(t, keyPEM)
|
||||||
|
|
||||||
|
crtAssertions := testutil.ValidateCertificate(t, string(caCrtPEM), string(crtPEM))
|
||||||
|
crtAssertions.RequireCommonName("some-common-name")
|
||||||
|
crtAssertions.RequireDNSName("some-dns-name")
|
||||||
|
crtAssertions.RequireDNSName("some-other-dns-name")
|
||||||
|
crtAssertions.RequireLifetime(time.Now(), time.Now().Add(time.Hour*24), time.Minute*10)
|
||||||
|
crtAssertions.RequireMatchesPrivateKey(string(keyPEM))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
17
internal/certauthority/dynamiccertauthority/testdata/ca-0.crt
vendored
Normal file
17
internal/certauthority/dynamiccertauthority/testdata/ca-0.crt
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICxjCCAa4CCQCawPQa0TyOmDANBgkqhkiG9w0BAQsFADAlMQ0wCwYDVQQDDAR0
|
||||||
|
dW5hMRQwEgYDVQQKDAtmaXNoLG1hcmxpbjAeFw0yMDA5MjMxMzMwMzZaFw0yMDEw
|
||||||
|
MjMxMzMwMzZaMCUxDTALBgNVBAMMBHR1bmExFDASBgNVBAoMC2Zpc2gsbWFybGlu
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxwLD08r2ZWh9FMGqq82O
|
||||||
|
CwraRKafmdgFk0qTpWS48di2A/Jz5R0zgx0fHYTs+pCL5FhfpDhTCYGTWMoXI2bW
|
||||||
|
7n6prDwrfNE+bsKdekEHM8MugdSFm2cPJONS7upIGODDIvK3zVGCg4Y0H/nE5Zf2
|
||||||
|
g+GrrY31a4tWYkS9b2J7ZxQlQ2ixsxICHvDN1JYwg6a90txOKJAJBK9lxtcY1//p
|
||||||
|
Dx0v/norGt92chMRq/QZYDELs2h2KoQeXmPYR7/MZYCavjV3Dcc55ueUksTQjusI
|
||||||
|
INYiYh/Jm3IKBSUjl0TjQaJz/q4+JOPb1D7aQIggOTuCTPXb858DHW0lgo64H3Zf
|
||||||
|
nQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB7qwje3H5Aw3OFssPuc7cIUrW7ohU9
|
||||||
|
mcKAq5T5KWRsX3buCkLRJO9BOhBpRIM2jcHSBZupC/JiwFwLMCA5/l0GE++9WLFE
|
||||||
|
9ta2eTjTV5Og7wwpWlljD0uqZ/IROEV206Nvs0k3L3XhM1eoz3MESh7Esmr3ZSwK
|
||||||
|
pNATzBhvhoYNRMYBM4Yy3poBzweLFlLnNW98jI4Gp+A/fA38cqVdmNXFsfNSpesu
|
||||||
|
nesMwBleDec2o8adOs3LtXhfufDLVrmpRJgYqRhgQe1X+PicdCeGRAEN+fH4PF2i
|
||||||
|
qza2f08YEWiobqdVsN9oIWupyobPrfEnuLjj1xYNSf4LTKvVUqeK7F8H
|
||||||
|
-----END CERTIFICATE-----
|
28
internal/certauthority/dynamiccertauthority/testdata/ca-0.key
vendored
Normal file
28
internal/certauthority/dynamiccertauthority/testdata/ca-0.key
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHAsPTyvZlaH0U
|
||||||
|
waqrzY4LCtpEpp+Z2AWTSpOlZLjx2LYD8nPlHTODHR8dhOz6kIvkWF+kOFMJgZNY
|
||||||
|
yhcjZtbufqmsPCt80T5uwp16QQczwy6B1IWbZw8k41Lu6kgY4MMi8rfNUYKDhjQf
|
||||||
|
+cTll/aD4autjfVri1ZiRL1vYntnFCVDaLGzEgIe8M3UljCDpr3S3E4okAkEr2XG
|
||||||
|
1xjX/+kPHS/+eisa33ZyExGr9BlgMQuzaHYqhB5eY9hHv8xlgJq+NXcNxznm55SS
|
||||||
|
xNCO6wgg1iJiH8mbcgoFJSOXRONBonP+rj4k49vUPtpAiCA5O4JM9dvznwMdbSWC
|
||||||
|
jrgfdl+dAgMBAAECggEATe9OQyXLomDuAu/o54kDJF3mplXeUMAhRtJAydeM+eEV
|
||||||
|
Pqx3KHVsE9+WrNe+ek2bCxx0r/oCwslEq9PQheOjLhjaV4HkweernHKIezT2HhZE
|
||||||
|
o0FX5UjRzG9drgR/oYZ7xLbqtfRCuUEYLqGAsyz0tCbvNXNPdgNGHAuxZ+pYS0AN
|
||||||
|
6DPOJKG1gUNSAzjU7U6g1UtOxr/qXVSZvBM+F7nTsbRKlnq/RYvLvfGLfnkQZvom
|
||||||
|
QxcyZmiC6fgmrpiCPN9poUKmB6jt3IWQJ9qyxadWI612fYrHWMEcXgNcxxOvCxyI
|
||||||
|
S1cGAq9Vw51pvGLlXsGzci3i4qemEDl8TDIp46C0oQKBgQD3ImywgoJn+IvTwtml
|
||||||
|
vFNfJ3s/J61SFv32u4iR9lim118mzvOi7FphjCfCj72cppfiuzI/F3Bsv0eR2qv9
|
||||||
|
9eXahvQe8egZvWPnW+f2GRYOAbHq/SmYA5kH2hHXOPHdJvfYj+fnEU+y6XXbFtp6
|
||||||
|
MLvmbgrPL6le1UHri3XPpcOjNQKBgQDOJmS+yK+ozZTpkNXYq2GNaVr5LaUhOePV
|
||||||
|
y+hvNj1IlhU7R97PcBe59izO73sYIUeOCmGM2cX3zGy7+upSyT2K3+Jh5BVpnwME
|
||||||
|
OqyrYFXfMJxwf9SuD9GJRiOtxtglBxcjNZEcninV1qynuhC9SaHjyhPnzcyZR/bB
|
||||||
|
Ipy4+mKvyQKBgQDktqs4P9BIQLHHbRDYXY4bBgL9086HplerPKuLyh0Ja1DYLbc1
|
||||||
|
FOAgXwF9AmJM07DFWNGLqjmVqwClb2a1RhlPQI41BVP0Xl5TA6+NBnJuzArImzsf
|
||||||
|
QFUj+yF/uWe9cA74EVZhdpf30DAObvwLEYcUHstKK6Xn6h1zzEFfxt1j8QKBgHjr
|
||||||
|
2Gh4e2E+xbyDcoyXaq9yPySue5ATzurXos2pOSVcs7OEItP24lP4bKwtmTy8OKa+
|
||||||
|
vB3Ml+0Ugit5sP1CgdD2JmpZSZ4c4b4XVLVp2rUCZKEwumYnbTdiZwdF/f8qO44x
|
||||||
|
m573v35pX/k6kRsXF9jv7eEovHyk077SOK/gXwbRAoGBAO5HfelAfPG4CFZdvj5z
|
||||||
|
sCCf8rr1Jpyk+1hILrI8hQUriL3bwStRppQHJajfswjZKP9pjyJXi/cWp7DvKBpo
|
||||||
|
DFGikRWzbSP/xkRNRW35bvGlcy8oit395dQJZOemrf9KoHQPLGT2pEnLDtqOtJAQ
|
||||||
|
YsquGd2q0TmDBVolI62cHjr2
|
||||||
|
-----END PRIVATE KEY-----
|
17
internal/certauthority/dynamiccertauthority/testdata/ca-1.crt
vendored
Normal file
17
internal/certauthority/dynamiccertauthority/testdata/ca-1.crt
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICxjCCAa4CCQC6B88ml9yfhzANBgkqhkiG9w0BAQsFADAlMQ0wCwYDVQQDDAR0
|
||||||
|
dW5hMRQwEgYDVQQKDAtmaXNoLG1hcmxpbjAeFw0yMDA5MjMxMzMwNDRaFw0yMDEw
|
||||||
|
MjMxMzMwNDRaMCUxDTALBgNVBAMMBHR1bmExFDASBgNVBAoMC2Zpc2gsbWFybGlu
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4rabilzx1UHfD52mRquC
|
||||||
|
S1t7YyeTrPWgS2VgUgD2NcghL0TL85P17KUh+OcEJ5N5QQMXgs8WMotnP786pg/Z
|
||||||
|
yX9U+KGAMIRla3k+YgX5qd45nN4ZMsCspK2gnfQbqbaPKA6oXRxXbqgrVy+kTx0/
|
||||||
|
QylSHrwOEZacObPhQ5wM+RGlkSCUJ4hlRr6POOLOSwqlcw7Uxw0I482FTK11jAaY
|
||||||
|
0EgItpW0ROsv7j6RhomGGGSuPa58qe9zwzz9nR8urnCJ4m+5Kw/hae3FxTSrOMmc
|
||||||
|
OiOXwyzEjihTax7Afvpe9rfjjXLZpz7EXyCmeA0BlFwjvuDx2oZ+/WitC/zUMKvM
|
||||||
|
XQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCA0DJsNEJC6sZletC+c6qHRKRjJDMY
|
||||||
|
0nsXaV6rFPASk+956rI+1DtBuc54UmhgO+VhoVda6lYeBUiXC7lMSU5T9MtFOgLA
|
||||||
|
OhWVSwL9XveLOEc2JrBaI8NKG2V9E0b5d6TJeIvxotv2En0gJIvachQy4JwM3fKr
|
||||||
|
mITVqeqvhWczLoFNIXmwYpIwqzhF6t2QmV7ecwXi8g0hoX9d9JVDs6qtvudlplIz
|
||||||
|
TjefZ6tNpNpXKBQCSI44ZydyNelz5q/A9j7iyG9tmU8jrCouii2DkUoFblrX/x+Q
|
||||||
|
bZ7was9gqUshQ3IHBxfynuNkKvT0VMSfpBFcTipW7QVFatCxZHb8idFD
|
||||||
|
-----END CERTIFICATE-----
|
28
internal/certauthority/dynamiccertauthority/testdata/ca-1.key
vendored
Normal file
28
internal/certauthority/dynamiccertauthority/testdata/ca-1.key
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDitpuKXPHVQd8P
|
||||||
|
naZGq4JLW3tjJ5Os9aBLZWBSAPY1yCEvRMvzk/XspSH45wQnk3lBAxeCzxYyi2c/
|
||||||
|
vzqmD9nJf1T4oYAwhGVreT5iBfmp3jmc3hkywKykraCd9Bupto8oDqhdHFduqCtX
|
||||||
|
L6RPHT9DKVIevA4Rlpw5s+FDnAz5EaWRIJQniGVGvo844s5LCqVzDtTHDQjjzYVM
|
||||||
|
rXWMBpjQSAi2lbRE6y/uPpGGiYYYZK49rnyp73PDPP2dHy6ucInib7krD+Fp7cXF
|
||||||
|
NKs4yZw6I5fDLMSOKFNrHsB++l72t+ONctmnPsRfIKZ4DQGUXCO+4PHahn79aK0L
|
||||||
|
/NQwq8xdAgMBAAECggEADnosuowu4ThgnyWpDZA8NMW1vmIzmvLgdpAVs4beMhcC
|
||||||
|
j3ssLvS+2oq8/aD68fAH6S/iW3cP6tBeGoMCosIWXTilW28suWxq8Y7/fdD13XQU
|
||||||
|
Z8EDCOum2qk/vXZuIctHnv44oCGEL8vLYLjpHCg49vf3h4DowRTGCyVCeEfJyEnK
|
||||||
|
ZUZzKtmohS88IhOfOoNC8qvXGhdJmbNmFngJqW1Z+4qozxpAYzjr4HwqFJF/YeQh
|
||||||
|
xKxjKSS5ikxdPqySdRxuUsIqFe72ZPIVo2NxVPkY+w75DZAqKDkemrDETM8TirMb
|
||||||
|
3n1lEabpiY1kWTF+UcthKhN4RV4RwYIRNFw/kpS7vQKBgQD77OPOSBsHqEw+Gykd
|
||||||
|
kVhTXJxspYI8pNQft1HvnlCNAEUsdT5jSW9O2PGjkUwsKQvSXGuMmYtnHdRv84G7
|
||||||
|
/or+yyZw2P3T1V01220Vj+fQh2YCrHqSEcmHlA7XSZblj4qs9rOrj5jRQbM8T78H
|
||||||
|
r09bum1wxIjHdRebPDCUATwFNwKBgQDmYVNwtF+kCUQNYYSDolxJSH9jjCOfGkWR
|
||||||
|
RHtimsM75jzzg8TX5XGJCJMO0rQ1k2A4YdVEwNCGUEOx/uX9Ey8LSyEaUGp8bXJ9
|
||||||
|
1sAjjPY6xmEV0gyQTFWR7EQHc/F90i+QaYaAbOvq479jwoamcKkDoY9wJsrQsxX/
|
||||||
|
AZAvX66FCwKBgHSYwyaqALiekAr+jxz8NCEA7/To9UoUD4lOU0HPyAA6a8mTyfgx
|
||||||
|
+K5JgizuBo85wBrwY2aDeh2TlMrrrNPRj4s1gukfxPrR+X3/vJEKNYQx5mi+Y0xP
|
||||||
|
pzJ0uBm0GX7N3KUI2UdCArx25/m1/vgTj2so8ZYLoDKQYwiZ5vHZUGopAoGBAOAZ
|
||||||
|
MFdjvd0M/ludzo9Vcjv+/5vQCB6OKbnDSdqC/QLZVdTzCpmQoT5RTuuOXqn28BQo
|
||||||
|
ZpJ4lN7yWMdeUk45SPvNWygDrXHX7RUnvsNWLXbC3lGhf4MmHd7SWuJ9EB36RTZO
|
||||||
|
z/1A9sQIQnZCFUT2NhJIKAVKVuNoMS9bT+wlQNg9AoGBALBj/5zbc2swgh1rB0Ob
|
||||||
|
7AQgPi9KHnsJcpZrY0jVwzQgQkYwtuaL4PL5eduNM/feaEOl54U3BCm5txa04H8N
|
||||||
|
Anp6RGPM3wHKSa2k0e3PtvICp/mL767kbxCuKcb1NhSpgAXAV5GLZNLxsWN5yJnZ
|
||||||
|
TVgvHlTpm6RMm7zDYHYyGFiO
|
||||||
|
-----END PRIVATE KEY-----
|
1
internal/certauthority/dynamiccertauthority/testdata/ca-bad.crt
vendored
Normal file
1
internal/certauthority/dynamiccertauthority/testdata/ca-bad.crt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
this is not a certificate
|
1
internal/certauthority/dynamiccertauthority/testdata/ca-bad.key
vendored
Normal file
1
internal/certauthority/dynamiccertauthority/testdata/ca-bad.key
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
this is not a key
|
@ -52,8 +52,8 @@ type Config struct {
|
|||||||
// discovery document.
|
// discovery document.
|
||||||
DiscoveryURLOverride *string
|
DiscoveryURLOverride *string
|
||||||
|
|
||||||
// DynamicCertProvider provides a setter and a getter to the Pinniped API's serving cert.
|
// DynamicServingCertProvider provides a setter and a getter to the Pinniped API's serving cert.
|
||||||
DynamicCertProvider dynamiccert.Provider
|
DynamicServingCertProvider dynamiccert.Provider
|
||||||
|
|
||||||
// ServingCertDuration is the validity period, in seconds, of the API serving certificate.
|
// ServingCertDuration is the validity period, in seconds, of the API serving certificate.
|
||||||
ServingCertDuration time.Duration
|
ServingCertDuration time.Duration
|
||||||
@ -75,6 +75,10 @@ type Config struct {
|
|||||||
// KubeCertAgentKeyPathAnnotation is the name of the annotation key that will be used when setting
|
// KubeCertAgentKeyPathAnnotation is the name of the annotation key that will be used when setting
|
||||||
// the best-guess path to the kube API's key. See kubecertagent.Info for more details.
|
// the best-guess path to the kube API's key. See kubecertagent.Info for more details.
|
||||||
KubeCertAgentKeyPathAnnotation string
|
KubeCertAgentKeyPathAnnotation string
|
||||||
|
|
||||||
|
// KubeCertAgentDynamicSigningCertProvider provides a setter and a getter to the Pinniped API's
|
||||||
|
// signing cert, i.e., the cert that it uses to sign certs for Pinniped clients wishing to login.
|
||||||
|
KubeCertAgentDynamicSigningCertProvider dynamiccert.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the controllers and their informers and return a function that will start them when called.
|
// Prepare the controllers and their informers and return a function that will start them when called.
|
||||||
@ -133,7 +137,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
apicerts.NewCertsObserverController(
|
apicerts.NewCertsObserverController(
|
||||||
c.ServerInstallationNamespace,
|
c.ServerInstallationNamespace,
|
||||||
c.NamesConfig.ServingCertificateSecret,
|
c.NamesConfig.ServingCertificateSecret,
|
||||||
c.DynamicCertProvider,
|
c.DynamicServingCertProvider,
|
||||||
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
),
|
),
|
||||||
|
@ -16,17 +16,11 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
|
|
||||||
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/login/v1alpha1"
|
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/login/v1alpha1"
|
||||||
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
|
|
||||||
"go.pinniped.dev/internal/apiserver"
|
"go.pinniped.dev/internal/apiserver"
|
||||||
"go.pinniped.dev/internal/certauthority/kubecertauthority"
|
"go.pinniped.dev/internal/certauthority/dynamiccertauthority"
|
||||||
"go.pinniped.dev/internal/controller/identityprovider/idpcache"
|
"go.pinniped.dev/internal/controller/identityprovider/idpcache"
|
||||||
"go.pinniped.dev/internal/controller/issuerconfig"
|
|
||||||
"go.pinniped.dev/internal/controllermanager"
|
"go.pinniped.dev/internal/controllermanager"
|
||||||
"go.pinniped.dev/internal/downward"
|
"go.pinniped.dev/internal/downward"
|
||||||
"go.pinniped.dev/internal/dynamiccert"
|
"go.pinniped.dev/internal/dynamiccert"
|
||||||
@ -130,21 +124,10 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
serverInstallationNamespace := podInfo.Namespace
|
serverInstallationNamespace := podInfo.Namespace
|
||||||
|
|
||||||
// Load the Kubernetes cluster signing CA.
|
// Load the Kubernetes cluster signing CA.
|
||||||
kubeCertAgentTemplate, kubeCertAgentLabelSelector := createKubeCertAgentTemplate(
|
kubeCertAgentTemplate := createKubeCertAgentTemplate(
|
||||||
&cfg.KubeCertAgentConfig,
|
&cfg.KubeCertAgentConfig,
|
||||||
serverInstallationNamespace,
|
serverInstallationNamespace,
|
||||||
)
|
)
|
||||||
// TODO replace this with our new controller
|
|
||||||
k8sClusterCA, shutdownCA, err := getClusterCASigner(
|
|
||||||
ctx,
|
|
||||||
serverInstallationNamespace,
|
|
||||||
cfg.NamesConfig.CredentialIssuerConfig,
|
|
||||||
kubeCertAgentLabelSelector,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer shutdownCA()
|
|
||||||
|
|
||||||
// Initialize the cache of active identity providers.
|
// Initialize the cache of active identity providers.
|
||||||
idpCache := idpcache.New()
|
idpCache := idpcache.New()
|
||||||
@ -154,23 +137,27 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
// is stored in a k8s Secret. Therefore it also effectively acting as
|
// is stored in a k8s Secret. Therefore it also effectively acting as
|
||||||
// an in-memory cache of what is stored in the k8s Secret, helping to
|
// an in-memory cache of what is stored in the k8s Secret, helping to
|
||||||
// keep incoming requests fast.
|
// keep incoming requests fast.
|
||||||
dynamicCertProvider := dynamiccert.New()
|
dynamicServingCertProvider := dynamiccert.New()
|
||||||
|
|
||||||
|
// This cert provider will be used to provide a signing key to the
|
||||||
|
// cert issuer used to issue certs to Pinniped clients wishing to login.
|
||||||
|
dynamicSigningCertProvider := dynamiccert.New()
|
||||||
|
|
||||||
// Prepare to start the controllers, but defer actually starting them until the
|
// Prepare to start the controllers, but defer actually starting them until the
|
||||||
// post start hook of the aggregated API server.
|
// post start hook of the aggregated API server.
|
||||||
startControllersFunc, err := controllermanager.PrepareControllers(
|
startControllersFunc, err := controllermanager.PrepareControllers(
|
||||||
&controllermanager.Config{
|
&controllermanager.Config{
|
||||||
ServerInstallationNamespace: serverInstallationNamespace,
|
ServerInstallationNamespace: serverInstallationNamespace,
|
||||||
NamesConfig: &cfg.NamesConfig,
|
NamesConfig: &cfg.NamesConfig,
|
||||||
DiscoveryURLOverride: cfg.DiscoveryInfo.URL,
|
DiscoveryURLOverride: cfg.DiscoveryInfo.URL,
|
||||||
DynamicCertProvider: dynamicCertProvider,
|
DynamicServingCertProvider: dynamicServingCertProvider,
|
||||||
//KubeAPISigningCertProvider: nil, // TODO pass this as a dynamiccert.New(), so it can be passed into the new controller
|
ServingCertDuration: time.Duration(*cfg.APIConfig.ServingCertificateConfig.DurationSeconds) * time.Second,
|
||||||
ServingCertDuration: time.Duration(*cfg.APIConfig.ServingCertificateConfig.DurationSeconds) * time.Second,
|
ServingCertRenewBefore: time.Duration(*cfg.APIConfig.ServingCertificateConfig.RenewBeforeSeconds) * time.Second,
|
||||||
ServingCertRenewBefore: time.Duration(*cfg.APIConfig.ServingCertificateConfig.RenewBeforeSeconds) * time.Second,
|
IDPCache: idpCache,
|
||||||
IDPCache: idpCache,
|
KubeCertAgentTemplate: kubeCertAgentTemplate,
|
||||||
KubeCertAgentTemplate: kubeCertAgentTemplate,
|
KubeCertAgentCertPathAnnotation: kubeCertAgentCertPathAnnotationKey,
|
||||||
KubeCertAgentCertPathAnnotation: kubeCertAgentCertPathAnnotationKey,
|
KubeCertAgentKeyPathAnnotation: kubeCertAgentKeyPathAnnotationKey,
|
||||||
KubeCertAgentKeyPathAnnotation: kubeCertAgentKeyPathAnnotationKey,
|
KubeCertAgentDynamicSigningCertProvider: dynamicSigningCertProvider,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -179,9 +166,9 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
|
|
||||||
// Get the aggregated API server config.
|
// Get the aggregated API server config.
|
||||||
aggregatedAPIServerConfig, err := getAggregatedAPIServerConfig(
|
aggregatedAPIServerConfig, err := getAggregatedAPIServerConfig(
|
||||||
dynamicCertProvider,
|
dynamicServingCertProvider,
|
||||||
idpCache,
|
idpCache,
|
||||||
k8sClusterCA, // TODO pass the same instance of dynamiccert.Provider as above, but wrapped into a new type that implements credentialrequest.CertIssuer, which should return ErrIncapableOfIssuingCertificates until the certs are available
|
dynamiccertauthority.New(dynamicSigningCertProvider),
|
||||||
startControllersFunc,
|
startControllersFunc,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -198,92 +185,6 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClusterCASigner(
|
|
||||||
ctx context.Context, serverInstallationNamespace string,
|
|
||||||
credentialIssuerConfigResourceName string,
|
|
||||||
kubeCertAgentLabelSelector string,
|
|
||||||
) (credentialrequest.CertIssuer, kubecertauthority.ShutdownFunc, error) {
|
|
||||||
// Load the Kubernetes client configuration.
|
|
||||||
kubeConfig, err := restclient.InClusterConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the core Kubernetes API.
|
|
||||||
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the pinniped API.
|
|
||||||
pinnipedClient, err := pinnipedclientset.NewForConfig(kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not initialize pinniped client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a clock tick that triggers a periodic refresh.
|
|
||||||
ticker := time.NewTicker(5 * time.Minute)
|
|
||||||
|
|
||||||
// Make a CA which uses the Kubernetes cluster API server's signing certs.
|
|
||||||
kubeCertAgentInfo := kubecertauthority.AgentInfo{
|
|
||||||
Namespace: "kube-system",
|
|
||||||
LabelSelector: kubeCertAgentLabelSelector,
|
|
||||||
CertPathAnnotation: kubeCertAgentCertPathAnnotationKey,
|
|
||||||
KeyPathAnnotation: kubeCertAgentKeyPathAnnotationKey,
|
|
||||||
}
|
|
||||||
k8sClusterCA, shutdownCA := kubecertauthority.New(
|
|
||||||
&kubeCertAgentInfo,
|
|
||||||
kubeClient,
|
|
||||||
kubecertauthority.NewPodCommandExecutor(kubeConfig, kubeClient),
|
|
||||||
ticker.C,
|
|
||||||
func() { // success callback
|
|
||||||
err = issuerconfig.CreateOrUpdateCredentialIssuerConfig(
|
|
||||||
ctx,
|
|
||||||
serverInstallationNamespace,
|
|
||||||
credentialIssuerConfigResourceName,
|
|
||||||
pinnipedClient,
|
|
||||||
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
|
|
||||||
configToUpdate.Status.Strategies = []configv1alpha1.CredentialIssuerConfigStrategy{
|
|
||||||
{
|
|
||||||
Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
|
|
||||||
Status: configv1alpha1.SuccessStrategyStatus,
|
|
||||||
Reason: configv1alpha1.FetchedKeyStrategyReason,
|
|
||||||
Message: "Key was fetched successfully",
|
|
||||||
LastUpdateTime: metav1.Now(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("error performing create or update on CredentialIssuerConfig to add strategy success: %s", err.Error())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
func(err error) { // error callback
|
|
||||||
if updateErr := issuerconfig.CreateOrUpdateCredentialIssuerConfig(
|
|
||||||
ctx,
|
|
||||||
serverInstallationNamespace,
|
|
||||||
credentialIssuerConfigResourceName,
|
|
||||||
pinnipedClient,
|
|
||||||
func(configToUpdate *configv1alpha1.CredentialIssuerConfig) {
|
|
||||||
configToUpdate.Status.Strategies = []configv1alpha1.CredentialIssuerConfigStrategy{
|
|
||||||
{
|
|
||||||
Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
|
|
||||||
Status: configv1alpha1.ErrorStrategyStatus,
|
|
||||||
Reason: configv1alpha1.CouldNotFetchKeyStrategyReason,
|
|
||||||
Message: err.Error(),
|
|
||||||
LastUpdateTime: metav1.Now(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
); updateErr != nil {
|
|
||||||
klog.Errorf("error performing create or update on CredentialIssuerConfig to add strategy error: %s", updateErr.Error())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return k8sClusterCA, func() { shutdownCA(); ticker.Stop() }, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a configuration for the aggregated API server.
|
// Create a configuration for the aggregated API server.
|
||||||
func getAggregatedAPIServerConfig(
|
func getAggregatedAPIServerConfig(
|
||||||
dynamicCertProvider dynamiccert.Provider,
|
dynamicCertProvider dynamiccert.Provider,
|
||||||
@ -324,7 +225,7 @@ func getAggregatedAPIServerConfig(
|
|||||||
return apiServerConfig, nil
|
return apiServerConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createKubeCertAgentTemplate(cfg *configapi.KubeCertAgentSpec, serverInstallationNamespace string) (*corev1.Pod, string) {
|
func createKubeCertAgentTemplate(cfg *configapi.KubeCertAgentSpec, serverInstallationNamespace string) *corev1.Pod {
|
||||||
terminateImmediately := int64(0)
|
terminateImmediately := int64(0)
|
||||||
pod := &corev1.Pod{
|
pod := &corev1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@ -355,6 +256,5 @@ func createKubeCertAgentTemplate(cfg *configapi.KubeCertAgentSpec, serverInstall
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
labelSelector := kubeCertAgentLabelKey + "="
|
return pod
|
||||||
return pod, labelSelector
|
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,12 @@ func (v *ValidCert) RequireMatchesPrivateKey(keyPEM string) {
|
|||||||
require.NoError(v.t, err)
|
require.NoError(v.t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequireCommonName asserts that the certificate contains the provided commonName.
|
||||||
|
func (v *ValidCert) RequireCommonName(commonName string) {
|
||||||
|
v.t.Helper()
|
||||||
|
require.Equal(v.t, commonName, v.parsed.Subject.CommonName)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateCertificate creates a certificate with the provided time bounds, and
|
// CreateCertificate creates a certificate with the provided time bounds, and
|
||||||
// returns the PEM representation of the certificate.
|
// returns the PEM representation of the certificate.
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user