diff --git a/internal/certauthority/dynamiccertauthority/dynamiccertauthority.go b/internal/certauthority/dynamiccertauthority/dynamiccertauthority.go new file mode 100644 index 000000000..6b65b620e --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/dynamiccertauthority.go @@ -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) +} diff --git a/internal/certauthority/dynamiccertauthority/dynamiccertauthority_test.go b/internal/certauthority/dynamiccertauthority/dynamiccertauthority_test.go new file mode 100644 index 000000000..1fdc561d6 --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/dynamiccertauthority_test.go @@ -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)) + } + }) + } +} diff --git a/internal/certauthority/dynamiccertauthority/testdata/ca-0.crt b/internal/certauthority/dynamiccertauthority/testdata/ca-0.crt new file mode 100644 index 000000000..985647868 --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/testdata/ca-0.crt @@ -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----- diff --git a/internal/certauthority/dynamiccertauthority/testdata/ca-0.key b/internal/certauthority/dynamiccertauthority/testdata/ca-0.key new file mode 100644 index 000000000..97d413179 --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/testdata/ca-0.key @@ -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----- diff --git a/internal/certauthority/dynamiccertauthority/testdata/ca-1.crt b/internal/certauthority/dynamiccertauthority/testdata/ca-1.crt new file mode 100644 index 000000000..6ad552357 --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/testdata/ca-1.crt @@ -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----- diff --git a/internal/certauthority/dynamiccertauthority/testdata/ca-1.key b/internal/certauthority/dynamiccertauthority/testdata/ca-1.key new file mode 100644 index 000000000..ca4b96a3e --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/testdata/ca-1.key @@ -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----- diff --git a/internal/certauthority/dynamiccertauthority/testdata/ca-bad.crt b/internal/certauthority/dynamiccertauthority/testdata/ca-bad.crt new file mode 100644 index 000000000..79752437f --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/testdata/ca-bad.crt @@ -0,0 +1 @@ +this is not a certificate diff --git a/internal/certauthority/dynamiccertauthority/testdata/ca-bad.key b/internal/certauthority/dynamiccertauthority/testdata/ca-bad.key new file mode 100644 index 000000000..99f3b8678 --- /dev/null +++ b/internal/certauthority/dynamiccertauthority/testdata/ca-bad.key @@ -0,0 +1 @@ +this is not a key diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index 9d3776af3..a089377f1 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -52,8 +52,8 @@ type Config struct { // discovery document. DiscoveryURLOverride *string - // DynamicCertProvider provides a setter and a getter to the Pinniped API's serving cert. - DynamicCertProvider dynamiccert.Provider + // DynamicServingCertProvider provides a setter and a getter to the Pinniped API's serving cert. + DynamicServingCertProvider dynamiccert.Provider // ServingCertDuration is the validity period, in seconds, of the API serving certificate. ServingCertDuration time.Duration @@ -75,6 +75,10 @@ type Config struct { // 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. 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. @@ -133,7 +137,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { apicerts.NewCertsObserverController( c.ServerInstallationNamespace, c.NamesConfig.ServingCertificateSecret, - c.DynamicCertProvider, + c.DynamicServingCertProvider, informers.installationNamespaceK8s.Core().V1().Secrets(), controllerlib.WithInformer, ), diff --git a/internal/server/server.go b/internal/server/server.go index 2eb484e8b..4e7147537 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -16,17 +16,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapiserver "k8s.io/apiserver/pkg/server" 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" - pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned" "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/issuerconfig" "go.pinniped.dev/internal/controllermanager" "go.pinniped.dev/internal/downward" "go.pinniped.dev/internal/dynamiccert" @@ -130,21 +124,10 @@ func (a *App) runServer(ctx context.Context) error { serverInstallationNamespace := podInfo.Namespace // Load the Kubernetes cluster signing CA. - kubeCertAgentTemplate, kubeCertAgentLabelSelector := createKubeCertAgentTemplate( + kubeCertAgentTemplate := createKubeCertAgentTemplate( &cfg.KubeCertAgentConfig, 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. 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 // an in-memory cache of what is stored in the k8s Secret, helping to // 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 // post start hook of the aggregated API server. startControllersFunc, err := controllermanager.PrepareControllers( &controllermanager.Config{ - ServerInstallationNamespace: serverInstallationNamespace, - NamesConfig: &cfg.NamesConfig, - DiscoveryURLOverride: cfg.DiscoveryInfo.URL, - DynamicCertProvider: dynamicCertProvider, - //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, - ServingCertRenewBefore: time.Duration(*cfg.APIConfig.ServingCertificateConfig.RenewBeforeSeconds) * time.Second, - IDPCache: idpCache, - KubeCertAgentTemplate: kubeCertAgentTemplate, - KubeCertAgentCertPathAnnotation: kubeCertAgentCertPathAnnotationKey, - KubeCertAgentKeyPathAnnotation: kubeCertAgentKeyPathAnnotationKey, + ServerInstallationNamespace: serverInstallationNamespace, + NamesConfig: &cfg.NamesConfig, + DiscoveryURLOverride: cfg.DiscoveryInfo.URL, + DynamicServingCertProvider: dynamicServingCertProvider, + ServingCertDuration: time.Duration(*cfg.APIConfig.ServingCertificateConfig.DurationSeconds) * time.Second, + ServingCertRenewBefore: time.Duration(*cfg.APIConfig.ServingCertificateConfig.RenewBeforeSeconds) * time.Second, + IDPCache: idpCache, + KubeCertAgentTemplate: kubeCertAgentTemplate, + KubeCertAgentCertPathAnnotation: kubeCertAgentCertPathAnnotationKey, + KubeCertAgentKeyPathAnnotation: kubeCertAgentKeyPathAnnotationKey, + KubeCertAgentDynamicSigningCertProvider: dynamicSigningCertProvider, }, ) if err != nil { @@ -179,9 +166,9 @@ func (a *App) runServer(ctx context.Context) error { // Get the aggregated API server config. aggregatedAPIServerConfig, err := getAggregatedAPIServerConfig( - dynamicCertProvider, + dynamicServingCertProvider, 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, ) if err != nil { @@ -198,92 +185,6 @@ func (a *App) runServer(ctx context.Context) error { 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. func getAggregatedAPIServerConfig( dynamicCertProvider dynamiccert.Provider, @@ -324,7 +225,7 @@ func getAggregatedAPIServerConfig( 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) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -355,6 +256,5 @@ func createKubeCertAgentTemplate(cfg *configapi.KubeCertAgentSpec, serverInstall }, }, } - labelSelector := kubeCertAgentLabelKey + "=" - return pod, labelSelector + return pod } diff --git a/internal/testutil/certs.go b/internal/testutil/certs.go index 100076fdd..dac365a32 100644 --- a/internal/testutil/certs.go +++ b/internal/testutil/certs.go @@ -75,6 +75,12 @@ func (v *ValidCert) RequireMatchesPrivateKey(keyPEM string) { 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 // returns the PEM representation of the certificate. //