diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ce704a5010d..d8f4f75aacea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes: +* feature: validate certificates that users want to use as a `provided` CA + [#565](https://github.com/Kong/kuma/pull/565) * fix: add MADS port to K8S install script [#564](https://github.com/Kong/kuma/pull/564) * feature: sanitize metrics for StatsD and Prometheus diff --git a/app/kumactl/cmd/manage/ca/provided_test.go b/app/kumactl/cmd/manage/ca/provided_test.go index e70489818111..30ed92318df8 100644 --- a/app/kumactl/cmd/manage/ca/provided_test.go +++ b/app/kumactl/cmd/manage/ca/provided_test.go @@ -2,21 +2,26 @@ package ca_test import ( "bytes" - "github.com/Kong/kuma/app/kumactl/pkg/ca" "io/ioutil" "path/filepath" + "github.com/Kong/kuma/app/kumactl/pkg/ca" + "github.com/Kong/kuma/app/kumactl/cmd" kumactl_cmd "github.com/Kong/kuma/app/kumactl/pkg/cmd" "github.com/Kong/kuma/pkg/catalog" catalog_client "github.com/Kong/kuma/pkg/catalog/client" kumactl_config "github.com/Kong/kuma/pkg/config/app/kumactl/v1alpha1" "github.com/Kong/kuma/pkg/core/ca/provided/rest/types" + error_types "github.com/Kong/kuma/pkg/core/rest/errors/types" test_catalog "github.com/Kong/kuma/pkg/test/catalog" "github.com/Kong/kuma/pkg/tls" "github.com/spf13/cobra" + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" ) @@ -25,6 +30,7 @@ var _ ca.ProvidedCaClient = &staticProvidedCaClient{} type staticProvidedCaClient struct { addMesh string addPair tls.KeyPair + addErr error deleteCertMesh string deleteCertId string @@ -38,6 +44,9 @@ type staticProvidedCaClient struct { func (s *staticProvidedCaClient) AddSigningCertificate(mesh string, pair tls.KeyPair) (types.SigningCert, error) { s.addMesh = mesh s.addPair = pair + if s.addErr != nil { + return types.SigningCert{}, s.addErr + } return types.SigningCert{ Id: "id-13456", }, nil @@ -92,7 +101,7 @@ var _ = Describe("kumactl manage provided ca", func() { rootCmd.SetOut(buf) }) - It("should add certificate", func() { + It("should add proper CA certificate", func() { // setup certBytes, err := ioutil.ReadFile(filepath.Join("testdata", "cert.pem")) Expect(err).ToNot(HaveOccurred()) @@ -122,6 +131,71 @@ var _ = Describe("kumactl manage provided ca", func() { Expect(buf.String()).To(Equal(`added certificate "id-13456"`)) }) + Describe("should not add improper CA certificate", func() { + + type testCase struct { + addErr string + expectedOut string + } + + DescribeTable("should reject invalid cert", + func(given testCase) { + // setup + + addErr := error_types.Error{} + // when + err := yaml.Unmarshal([]byte(given.addErr), &addErr) + // then + Expect(err).ToNot(HaveOccurred()) + // and + client.addErr = &addErr + + // given + rootCmd.SetArgs([]string{ + "manage", "ca", "provided", "certificates", "add", + "--mesh", "demo", + "--key-file", filepath.Join("testdata", "cert.key"), + "--cert-file", filepath.Join("testdata", "cert.pem"), + }) + + // when + err = rootCmd.Execute() + // then + Expect(err).To(HaveOccurred()) + + // and + Expect(buf.String()).To(Equal(given.expectedOut)) + }, + Entry("1 violation", testCase{ + addErr: ` + title: 'Could not add signing cert' + details: 'Resource is not valid' + causes: + - field: cert + message: "key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage)" +`, + expectedOut: `Error: Could not add signing cert (Resource is not valid) +* cert: key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage) +`, + }), + Entry("N violations", testCase{ + addErr: ` + title: 'Could not add signing cert' + details: 'Resource is not valid' + causes: + - field: cert + message: "basic constraint 'CA' must be set to 'true' (see X509-SVID: 4.1. Basic Constraints)" + - field: cert + message: "key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage)" +`, + expectedOut: `Error: Could not add signing cert (Resource is not valid) +* cert: basic constraint 'CA' must be set to 'true' (see X509-SVID: 4.1. Basic Constraints) +* cert: key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage) +`, + }), + ) + }) + It("should delete certificate", func() { // given rootCmd.SetArgs([]string{ diff --git a/pkg/core/ca/builtin/issuer/issuer.go b/pkg/core/ca/builtin/issuer/issuer.go index e377734391b6..582c9939bee8 100644 --- a/pkg/core/ca/builtin/issuer/issuer.go +++ b/pkg/core/ca/builtin/issuer/issuer.go @@ -1,14 +1,12 @@ package issuer import ( - "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" - "encoding/pem" "math/big" "net/url" "time" @@ -35,7 +33,7 @@ func NewRootCA(mesh string) (*util_tls.KeyPair, error) { if err != nil { return nil, errors.Wrap(err, "failed to generate X509 certificate") } - return keyPair(key, cert) + return util_tls.ToKeyPair(key, cert) } func newCACert(signer crypto.Signer, trustDomain string) ([]byte, error) { @@ -73,7 +71,7 @@ func NewWorkloadCert(ca util_tls.KeyPair, mesh string, workload string) (*util_t if err != nil { return nil, errors.Wrap(err, "failed to generate X509 certificate") } - return keyPair(workloadKey, workloadCert) + return util_tls.ToKeyPair(workloadKey, workloadCert) } func newWorkloadCert(signer crypto.PrivateKey, parent *x509.Certificate, trustDomain string, workload string, publicKey crypto.PublicKey) ([]byte, error) { @@ -111,41 +109,3 @@ func loadKeyPair(pair util_tls.KeyPair) (crypto.PrivateKey, *x509.Certificate, e } return root.PrivateKey, rootCert, nil } - -func keyPair(key interface{}, cert []byte) (*util_tls.KeyPair, error) { - keyPem, err := pemEncodeKey(key) - if err != nil { - return nil, errors.Wrap(err, "failed to PEM encode a private key") - } - certPem, err := pemEncodeCert(cert) - if err != nil { - return nil, errors.Wrap(err, "failed to PEM encode a certificate") - } - return &util_tls.KeyPair{ - CertPEM: certPem, - KeyPEM: keyPem, - }, nil -} - -func pemEncodeKey(priv interface{}) ([]byte, error) { - var block *pem.Block - switch k := priv.(type) { - case *rsa.PrivateKey: - block = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} - default: - return nil, errors.Errorf("unsupported private key type %T", priv) - } - var keyBuf bytes.Buffer - if err := pem.Encode(&keyBuf, block); err != nil { - return nil, err - } - return keyBuf.Bytes(), nil -} - -func pemEncodeCert(derBytes []byte) ([]byte, error) { - var certBuf bytes.Buffer - if err := pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return nil, err - } - return certBuf.Bytes(), nil -} diff --git a/pkg/core/ca/provided/ca_cert_validator.go b/pkg/core/ca/provided/ca_cert_validator.go new file mode 100644 index 000000000000..9ec8566ac101 --- /dev/null +++ b/pkg/core/ca/provided/ca_cert_validator.go @@ -0,0 +1,53 @@ +package provided + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + + "github.com/Kong/kuma/pkg/core/validators" + + util_tls "github.com/Kong/kuma/pkg/tls" +) + +func ValidateCaCert(signingPair util_tls.KeyPair) error { + err := validateCaCert(signingPair) + return err.OrNil() +} + +func validateCaCert(signingPair util_tls.KeyPair) (verr validators.ValidationError) { + tlsKeyPair, err := tls.X509KeyPair(signingPair.CertPEM, signingPair.KeyPEM) + if err != nil { + verr.AddViolation(".", fmt.Sprintf("not a valid TLS key pair: %s", err)) + return + } + if len(tlsKeyPair.Certificate) != 1 { + verr.AddViolation("cert", "certificate must be a root CA (certificate chains are not allowed)") // Envoy constraint + return + } + cert, err := x509.ParseCertificate(tlsKeyPair.Certificate[0]) + if err != nil { + verr.AddViolation("cert", fmt.Sprintf("not a valid x509 certificate: %s", err)) + return + } + if cert.Issuer.String() != cert.Subject.String() { + verr.AddViolation("cert", "certificate must be self-signed (intermediate CAs are not allowed)") // Envoy constraint + } + if !cert.IsCA { + verr.AddViolation("cert", "basic constraint 'CA' must be set to 'true' (see X509-SVID: 4.1. Basic Constraints)") + } + if cert.KeyUsage&x509.KeyUsageCertSign == 0 { + verr.AddViolation("cert", "key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage)") + } + if cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 { + verr.AddViolation("cert", "key usage extension 'digitalSignature' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)") + } + if cert.KeyUsage&x509.KeyUsageKeyAgreement != 0 { + verr.AddViolation("cert", "key usage extension 'keyAgreement' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)") + } + if cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0 { + verr.AddViolation("cert", "key usage extension 'keyEncipherment' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)") + } + + return +} diff --git a/pkg/core/ca/provided/ca_cert_validator_test.go b/pkg/core/ca/provided/ca_cert_validator_test.go new file mode 100644 index 000000000000..1e3b101858c5 --- /dev/null +++ b/pkg/core/ca/provided/ca_cert_validator_test.go @@ -0,0 +1,439 @@ +package provided_test + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "math/big" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "github.com/ghodss/yaml" + "github.com/pkg/errors" + + . "github.com/Kong/kuma/pkg/core/ca/provided" + + builtin_issuer "github.com/Kong/kuma/pkg/core/ca/builtin/issuer" + util_tls "github.com/Kong/kuma/pkg/tls" +) + +var _ = Describe("ValidateCaCert()", func() { + + It("should accept proper CA certificates", func() { + // when + signingPair, err := builtin_issuer.NewRootCA("demo") + // then + Expect(err).ToNot(HaveOccurred()) + + // when + err = ValidateCaCert(*signingPair) + // then + Expect(err).ToNot(HaveOccurred()) + }) + + NewSelfSignedCert := func(newTemplate func() *x509.Certificate) (*util_tls.KeyPair, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a private key") + } + template := newTemplate() + template.PublicKey = key.Public() + cert, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) + if err != nil { + return nil, errors.Wrap(err, "failed to sign X509 certificate") + } + return util_tls.ToKeyPair(key, cert) + } + + type testCase struct { + input util_tls.KeyPair + expectedErr string + } + + DescribeTable("should reject invalid input", + func(givenFunc func() testCase) { + given := givenFunc() + + // when + err := ValidateCaCert(given.input) + // then + Expect(err).ToNot(BeNil()) + + // when + actual, err := yaml.Marshal(err) + // then + Expect(err).ToNot(HaveOccurred()) + + // and + Expect(actual).To(MatchYAML(given.expectedErr)) + }, + Entry("empty key pair", func() testCase { + return testCase{ + expectedErr: ` + violations: + - field: . + message: 'not a valid TLS key pair: tls: failed to find any PEM data in certificate input' +`, + } + }), + Entry("bad plain text values", func() testCase { + return testCase{ + expectedErr: ` + violations: + - field: . + message: 'not a valid TLS key pair: tls: failed to find any PEM data in certificate input' +`, + input: util_tls.KeyPair{ + CertPEM: []byte("CERT"), + KeyPEM: []byte("KEY"), + }, + } + }), + Entry("cert and key don't match", func() testCase { + return testCase{ + expectedErr: ` + violations: + - field: . + message: 'not a valid TLS key pair: tls: private key does not match public key' +`, + input: util_tls.KeyPair{ + CertPEM: []byte(` +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAwMQ0wCwYDVQQKEwRLdW1h +MQ0wCwYDVQQLEwRNZXNoMRAwDgYDVQQDEwdkZWZhdWx0MB4XDTIwMDEyOTE2MDgw +NFoXDTMwMDEyNjE2MDgxNFowMDENMAsGA1UEChMES3VtYTENMAsGA1UECxMETWVz +aDEQMA4GA1UEAxMHZGVmYXVsdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANhewNnHZI0f+55vsm+iGej9NAYFtCb2FNzFHZlGcu0F07YSAyorPJuM+6V3 +BFcY2IkWHL8WOooNmJ0X/yzBd4RSrb3TacGKtaDayRjTo8JOW7Nlh+WvwR18KHjC +QXDjlqkmfExdYIUZjOqJOhu9nO59fqz0SJFvo2WKkkP7CaTLQXt1p3+Hm1Xo5WCX +ZfD7W57YhNBZZLljip/N8pDL7b2Vkhe+txbv/PqVrDRMGoyRBnPNAfS7SPocRbcE +S9th2CesNu+Iwltu4gJBOQbpydBIjJvr1zrx/zxbxM+EbqbGr6gwquGvKTyXHq20 +u5CE4tWy3GKSh5LEVItPS066d5UCAwEAAaNAMD4wDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wGwYDVR0RBBQwEoYQc3BpZmZlOi8vZGVmYXVsdDANBgkq +hkiG9w0BAQsFAAOCAQEAMvMqCzbjEveuMlTch9q+/6KcFVUkwQcTcxxs0MPnw5Lw +hY6xo7FvIHNLJDRlShoAyjI6OJZobJ7PFaFnIXWlNcN1F+gA9OZSSWYgJNJl4zee +eS2pHgbxZ6OJqCbDGYWekF3d1vEcI3gaRfxFDVa8eJFBq+B0v2Pho8A2bY5srO0S +LG//nB619RAlzta6BxweiCmFxPyB+oqJl3X9NfWC7988CzfZAqHA+CqO6tJS5i53 +WMciH6+W7o9wdsBrfFVx2tGZqc4bZgoZptHFieqz7YBnT0Ozg+NwBU6apAtAc5Ym +DMoTRP2Vo+BEm4uS4GcIFZYqrOsPuuyMuBd0NDE33g== +-----END CERTIFICATE----- +`), + KeyPEM: []byte(` +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvh4DhQDuE/oXjEJzt1TmgmM6s5DtsIYIEPssxdDiDEjmJzhG +APd0BmAfrz3/fXZ6CqqqfAvTKkTDGjSN0hGtt1thF72weNfmQX9c1gUukEA0gERS +OsSWlzp8Azc5fjXtl5jiQkvaapNoQ8PljLc8SEsgndvdxD1tDVWN6h90bMgtuqyK +M7Fn/1+nwhRExKRTOSlR0iS9eddWPcN3HNGrl3vjCF0oUF/79+nJJqeaXawt0+2P +CMkxY6T6yzFaS/h0Oy3mXLOQ1H5n3cZ2MSEMKQSs9ff8gzeHpWyXpgB1XEKNcUvi +rVKfaJ/WE5v0rieG89B/YnQTduQnrwmPVuwm4wIDAQABAoIBAQCxKKrC7+DqwKvc +ybem6Ph8HBeBaNX1HpC5sjVAiKt8IxpFBc1F7VEy97POywkfUp3a/rorKaG2y6i6 +7KoTTOIB8KcDRoIBub4Y3qQV03JWfV3vALtXhAWIGrmhDX8Hux0RnSeJ+8EmewI3 +034+qCkGfOuB7nYy/cJ3IHhD6NfG3Q3FrBrGfsI2TGEeGmPJ2Xi8ZyfbluRb/1Bt +NesS6pDbRpZ5/IoauLUtITY3bazpzghm2tJNdrJIP7ohaoMF0WYciPyD5xpNlykt +V8Q2jzNmPYVXuUpi4oPekq4Mg1vd/LPS/JE558Am1LEiXrycelGNrDvJW7hTDLVx +DLRFuMMxAoGBAMkjupL3mxAfNytXM++WxJbdWPuw/vvAeN60ifFu6RUrMs/aXocn +4xSunNF58O2aRfSq/B9LJ+pXtmdITV+Bu0Y1XefKtNUNoqIapAbA8gAWUcFSkDRd +999rh0vWPbx4d3k69iS6xIjVaRcxeuaBbKRWqUcrxDuAydhwTLIRMD1vAoGBAPH4 +quLGkr1MdTeZ3qPAWc9mGelp0LhHukjnLB+nMdI73OH7IlX5or11yr6La/+sTmmQ +fI+oITLuCyey7VnWBDhrPmWFGA1BmZIVDqjkJJNwyWQO7N27rQEQoNKm5n6Q+boy +StNKa/ljduYXCjsBndOmF1wSrAwL+u9rQ3x4k9vNAoGAGY5vm1LYofDFar1WvP90 +FRMkxj4T99rZwLpBuKp19RmbCCvfzN51jOAuzrLmuNncP50mEbfT54OjinX2Vsc+ +C0qmltf7qAJmgqBN7QnA9d/gHWcnKXAzGXEpLKqZB4Rq8b1bHwmYBSbQhoDj87vI +GQ1lzsQx17mia9zA8fMbJQMCgYB0D+2PpuW9rM3QpJp4+wtZAsVNAzddHPKKg2/T +ovOvvoz9S+M1T+8yZyyfZuqfkTtvQSGuGlwKPMnW+ekFHTWbBj3Ani1iNmP+AOGu +OvgcTI4c01fkJ2AdUaeCQxHuBYXzPKpNXLYbwgzG4qhCk0zrtxAfVsl1Yc20R0Pw +kTmCxQKBgQCzd/OOLm7vDEUqYNUNgKlf8I9xM84IwUy+pJP8RaeFDtFj7tVDpY2P +GXHBXcIBDRPnmBxC7cGHCB3KBWJp11smw2qA0ZgmBIShNm1RDHf/1h0yOxSz2+fB +bgeEDefxTxoTMgJ1urwl0KX6R9dbv9YWZWJXk2DQj6UwkMEyXpc+kw== +-----END RSA PRIVATE KEY----- +`), + }, + } + }), + Entry("chain of CAs", func() testCase { + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "certificate must be a root CA (certificate chains are not allowed)" +`, + input: util_tls.KeyPair{ + CertPEM: []byte(` +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAwMQ0wCwYDVQQKEwRLdW1h +MQ0wCwYDVQQLEwRNZXNoMRAwDgYDVQQDEwdkZWZhdWx0MB4XDTIwMDEyOTE2MDgw +NFoXDTMwMDEyNjE2MDgxNFowQDENMAsGA1UEChMES3VtYTEdMAsGA1UECxMETWVz +aDAOBgNVBAsTB2xldmVsLTExEDAOBgNVBAMTB2RlZmF1bHQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC+HgOFAO4T+heMQnO3VOaCYzqzkO2whggQ+yzF +0OIMSOYnOEYA93QGYB+vPf99dnoKqqp8C9MqRMMaNI3SEa23W2EXvbB41+ZBf1zW +BS6QQDSARFI6xJaXOnwDNzl+Ne2XmOJCS9pqk2hDw+WMtzxISyCd293EPW0NVY3q +H3RsyC26rIozsWf/X6fCFETEpFM5KVHSJL1511Y9w3cc0auXe+MIXShQX/v36ckm +p5pdrC3T7Y8IyTFjpPrLMVpL+HQ7LeZcs5DUfmfdxnYxIQwpBKz19/yDN4elbJem +AHVcQo1xS+KtUp9on9YTm/SuJ4bz0H9idBN25CevCY9W7CbjAgMBAAGjQDA+MA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MBsGA1UdEQQUMBKGEHNwaWZm +ZTovL2RlZmF1bHQwDQYJKoZIhvcNAQELBQADggEBACVXnYWCCrji551pbJsOCGYJ +GEqlvcwNnnYdykas4GrfsbW2rglmaXv0uG8iH2sAH+4/MjGjnlQ6Y6Fj7mDFnidj +ugU964sEDnLuU0CtaIpHl7VZ13I0EzmfY+GsCrcIXIxbAxwWTJhz77XqbHe3baLx +Sh9wHgz/aZuy99rq9OoAvUALEaIfxrvUsVs25jLuv0Xzy57B2Dpqo0odshDA4WSS +MynQnSX7aFg1jqZQL4YjPHryEQQRj8mgjqiWp8M4/PHq5s09zDMB0DCag0QtdC/k +ydtqRoojiRS2fXY8DhFRqqRVBqLvA+7eTEKpzfjUTyEovMqxIM2n4U5MSGKQlbM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAwMQ0wCwYDVQQKEwRLdW1h +MQ0wCwYDVQQLEwRNZXNoMRAwDgYDVQQDEwdkZWZhdWx0MB4XDTIwMDEyOTE2MDgw +NFoXDTMwMDEyNjE2MDgxNFowMDENMAsGA1UEChMES3VtYTENMAsGA1UECxMETWVz +aDEQMA4GA1UEAxMHZGVmYXVsdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANhewNnHZI0f+55vsm+iGej9NAYFtCb2FNzFHZlGcu0F07YSAyorPJuM+6V3 +BFcY2IkWHL8WOooNmJ0X/yzBd4RSrb3TacGKtaDayRjTo8JOW7Nlh+WvwR18KHjC +QXDjlqkmfExdYIUZjOqJOhu9nO59fqz0SJFvo2WKkkP7CaTLQXt1p3+Hm1Xo5WCX +ZfD7W57YhNBZZLljip/N8pDL7b2Vkhe+txbv/PqVrDRMGoyRBnPNAfS7SPocRbcE +S9th2CesNu+Iwltu4gJBOQbpydBIjJvr1zrx/zxbxM+EbqbGr6gwquGvKTyXHq20 +u5CE4tWy3GKSh5LEVItPS066d5UCAwEAAaNAMD4wDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wGwYDVR0RBBQwEoYQc3BpZmZlOi8vZGVmYXVsdDANBgkq +hkiG9w0BAQsFAAOCAQEAMvMqCzbjEveuMlTch9q+/6KcFVUkwQcTcxxs0MPnw5Lw +hY6xo7FvIHNLJDRlShoAyjI6OJZobJ7PFaFnIXWlNcN1F+gA9OZSSWYgJNJl4zee +eS2pHgbxZ6OJqCbDGYWekF3d1vEcI3gaRfxFDVa8eJFBq+B0v2Pho8A2bY5srO0S +LG//nB619RAlzta6BxweiCmFxPyB+oqJl3X9NfWC7988CzfZAqHA+CqO6tJS5i53 +WMciH6+W7o9wdsBrfFVx2tGZqc4bZgoZptHFieqz7YBnT0Ozg+NwBU6apAtAc5Ym +DMoTRP2Vo+BEm4uS4GcIFZYqrOsPuuyMuBd0NDE33g== +-----END CERTIFICATE----- +`), + KeyPEM: []byte(` +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvh4DhQDuE/oXjEJzt1TmgmM6s5DtsIYIEPssxdDiDEjmJzhG +APd0BmAfrz3/fXZ6CqqqfAvTKkTDGjSN0hGtt1thF72weNfmQX9c1gUukEA0gERS +OsSWlzp8Azc5fjXtl5jiQkvaapNoQ8PljLc8SEsgndvdxD1tDVWN6h90bMgtuqyK +M7Fn/1+nwhRExKRTOSlR0iS9eddWPcN3HNGrl3vjCF0oUF/79+nJJqeaXawt0+2P +CMkxY6T6yzFaS/h0Oy3mXLOQ1H5n3cZ2MSEMKQSs9ff8gzeHpWyXpgB1XEKNcUvi +rVKfaJ/WE5v0rieG89B/YnQTduQnrwmPVuwm4wIDAQABAoIBAQCxKKrC7+DqwKvc +ybem6Ph8HBeBaNX1HpC5sjVAiKt8IxpFBc1F7VEy97POywkfUp3a/rorKaG2y6i6 +7KoTTOIB8KcDRoIBub4Y3qQV03JWfV3vALtXhAWIGrmhDX8Hux0RnSeJ+8EmewI3 +034+qCkGfOuB7nYy/cJ3IHhD6NfG3Q3FrBrGfsI2TGEeGmPJ2Xi8ZyfbluRb/1Bt +NesS6pDbRpZ5/IoauLUtITY3bazpzghm2tJNdrJIP7ohaoMF0WYciPyD5xpNlykt +V8Q2jzNmPYVXuUpi4oPekq4Mg1vd/LPS/JE558Am1LEiXrycelGNrDvJW7hTDLVx +DLRFuMMxAoGBAMkjupL3mxAfNytXM++WxJbdWPuw/vvAeN60ifFu6RUrMs/aXocn +4xSunNF58O2aRfSq/B9LJ+pXtmdITV+Bu0Y1XefKtNUNoqIapAbA8gAWUcFSkDRd +999rh0vWPbx4d3k69iS6xIjVaRcxeuaBbKRWqUcrxDuAydhwTLIRMD1vAoGBAPH4 +quLGkr1MdTeZ3qPAWc9mGelp0LhHukjnLB+nMdI73OH7IlX5or11yr6La/+sTmmQ +fI+oITLuCyey7VnWBDhrPmWFGA1BmZIVDqjkJJNwyWQO7N27rQEQoNKm5n6Q+boy +StNKa/ljduYXCjsBndOmF1wSrAwL+u9rQ3x4k9vNAoGAGY5vm1LYofDFar1WvP90 +FRMkxj4T99rZwLpBuKp19RmbCCvfzN51jOAuzrLmuNncP50mEbfT54OjinX2Vsc+ +C0qmltf7qAJmgqBN7QnA9d/gHWcnKXAzGXEpLKqZB4Rq8b1bHwmYBSbQhoDj87vI +GQ1lzsQx17mia9zA8fMbJQMCgYB0D+2PpuW9rM3QpJp4+wtZAsVNAzddHPKKg2/T +ovOvvoz9S+M1T+8yZyyfZuqfkTtvQSGuGlwKPMnW+ekFHTWbBj3Ani1iNmP+AOGu +OvgcTI4c01fkJ2AdUaeCQxHuBYXzPKpNXLYbwgzG4qhCk0zrtxAfVsl1Yc20R0Pw +kTmCxQKBgQCzd/OOLm7vDEUqYNUNgKlf8I9xM84IwUy+pJP8RaeFDtFj7tVDpY2P +GXHBXcIBDRPnmBxC7cGHCB3KBWJp11smw2qA0ZgmBIShNm1RDHf/1h0yOxSz2+fB +bgeEDefxTxoTMgJ1urwl0KX6R9dbv9YWZWJXk2DQj6UwkMEyXpc+kw== +-----END RSA PRIVATE KEY----- +`), + }, + } + }), + Entry("not a self-signed certificate", func() testCase { + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "certificate must be self-signed (intermediate CAs are not allowed)" +`, + input: util_tls.KeyPair{ + CertPEM: []byte(` +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAwMQ0wCwYDVQQKEwRLdW1h +MQ0wCwYDVQQLEwRNZXNoMRAwDgYDVQQDEwdkZWZhdWx0MB4XDTIwMDEyOTE2MDgw +NFoXDTMwMDEyNjE2MDgxNFowQDENMAsGA1UEChMES3VtYTEdMAsGA1UECxMETWVz +aDAOBgNVBAsTB2xldmVsLTExEDAOBgNVBAMTB2RlZmF1bHQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC+HgOFAO4T+heMQnO3VOaCYzqzkO2whggQ+yzF +0OIMSOYnOEYA93QGYB+vPf99dnoKqqp8C9MqRMMaNI3SEa23W2EXvbB41+ZBf1zW +BS6QQDSARFI6xJaXOnwDNzl+Ne2XmOJCS9pqk2hDw+WMtzxISyCd293EPW0NVY3q +H3RsyC26rIozsWf/X6fCFETEpFM5KVHSJL1511Y9w3cc0auXe+MIXShQX/v36ckm +p5pdrC3T7Y8IyTFjpPrLMVpL+HQ7LeZcs5DUfmfdxnYxIQwpBKz19/yDN4elbJem +AHVcQo1xS+KtUp9on9YTm/SuJ4bz0H9idBN25CevCY9W7CbjAgMBAAGjQDA+MA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MBsGA1UdEQQUMBKGEHNwaWZm +ZTovL2RlZmF1bHQwDQYJKoZIhvcNAQELBQADggEBACVXnYWCCrji551pbJsOCGYJ +GEqlvcwNnnYdykas4GrfsbW2rglmaXv0uG8iH2sAH+4/MjGjnlQ6Y6Fj7mDFnidj +ugU964sEDnLuU0CtaIpHl7VZ13I0EzmfY+GsCrcIXIxbAxwWTJhz77XqbHe3baLx +Sh9wHgz/aZuy99rq9OoAvUALEaIfxrvUsVs25jLuv0Xzy57B2Dpqo0odshDA4WSS +MynQnSX7aFg1jqZQL4YjPHryEQQRj8mgjqiWp8M4/PHq5s09zDMB0DCag0QtdC/k +ydtqRoojiRS2fXY8DhFRqqRVBqLvA+7eTEKpzfjUTyEovMqxIM2n4U5MSGKQlbM= +-----END CERTIFICATE----- + +`), + KeyPEM: []byte(` +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvh4DhQDuE/oXjEJzt1TmgmM6s5DtsIYIEPssxdDiDEjmJzhG +APd0BmAfrz3/fXZ6CqqqfAvTKkTDGjSN0hGtt1thF72weNfmQX9c1gUukEA0gERS +OsSWlzp8Azc5fjXtl5jiQkvaapNoQ8PljLc8SEsgndvdxD1tDVWN6h90bMgtuqyK +M7Fn/1+nwhRExKRTOSlR0iS9eddWPcN3HNGrl3vjCF0oUF/79+nJJqeaXawt0+2P +CMkxY6T6yzFaS/h0Oy3mXLOQ1H5n3cZ2MSEMKQSs9ff8gzeHpWyXpgB1XEKNcUvi +rVKfaJ/WE5v0rieG89B/YnQTduQnrwmPVuwm4wIDAQABAoIBAQCxKKrC7+DqwKvc +ybem6Ph8HBeBaNX1HpC5sjVAiKt8IxpFBc1F7VEy97POywkfUp3a/rorKaG2y6i6 +7KoTTOIB8KcDRoIBub4Y3qQV03JWfV3vALtXhAWIGrmhDX8Hux0RnSeJ+8EmewI3 +034+qCkGfOuB7nYy/cJ3IHhD6NfG3Q3FrBrGfsI2TGEeGmPJ2Xi8ZyfbluRb/1Bt +NesS6pDbRpZ5/IoauLUtITY3bazpzghm2tJNdrJIP7ohaoMF0WYciPyD5xpNlykt +V8Q2jzNmPYVXuUpi4oPekq4Mg1vd/LPS/JE558Am1LEiXrycelGNrDvJW7hTDLVx +DLRFuMMxAoGBAMkjupL3mxAfNytXM++WxJbdWPuw/vvAeN60ifFu6RUrMs/aXocn +4xSunNF58O2aRfSq/B9LJ+pXtmdITV+Bu0Y1XefKtNUNoqIapAbA8gAWUcFSkDRd +999rh0vWPbx4d3k69iS6xIjVaRcxeuaBbKRWqUcrxDuAydhwTLIRMD1vAoGBAPH4 +quLGkr1MdTeZ3qPAWc9mGelp0LhHukjnLB+nMdI73OH7IlX5or11yr6La/+sTmmQ +fI+oITLuCyey7VnWBDhrPmWFGA1BmZIVDqjkJJNwyWQO7N27rQEQoNKm5n6Q+boy +StNKa/ljduYXCjsBndOmF1wSrAwL+u9rQ3x4k9vNAoGAGY5vm1LYofDFar1WvP90 +FRMkxj4T99rZwLpBuKp19RmbCCvfzN51jOAuzrLmuNncP50mEbfT54OjinX2Vsc+ +C0qmltf7qAJmgqBN7QnA9d/gHWcnKXAzGXEpLKqZB4Rq8b1bHwmYBSbQhoDj87vI +GQ1lzsQx17mia9zA8fMbJQMCgYB0D+2PpuW9rM3QpJp4+wtZAsVNAzddHPKKg2/T +ovOvvoz9S+M1T+8yZyyfZuqfkTtvQSGuGlwKPMnW+ekFHTWbBj3Ani1iNmP+AOGu +OvgcTI4c01fkJ2AdUaeCQxHuBYXzPKpNXLYbwgzG4qhCk0zrtxAfVsl1Yc20R0Pw +kTmCxQKBgQCzd/OOLm7vDEUqYNUNgKlf8I9xM84IwUy+pJP8RaeFDtFj7tVDpY2P +GXHBXcIBDRPnmBxC7cGHCB3KBWJp11smw2qA0ZgmBIShNm1RDHf/1h0yOxSz2+fB +bgeEDefxTxoTMgJ1urwl0KX6R9dbv9YWZWJXk2DQj6UwkMEyXpc+kw== +-----END RSA PRIVATE KEY----- +`), + }, + } + }), + Entry("certificate without basic constraint `CA`", func() testCase { + // when + keyPair, err := NewSelfSignedCert(func() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(0), + } + }) + // then + Expect(err).ToNot(HaveOccurred()) + + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "basic constraint 'CA' must be set to 'true' (see X509-SVID: 4.1. Basic Constraints)" + - field: cert + message: "key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage)" +`, + input: *keyPair, + } + }), + Entry("certificate without key usage extension `keyCertSign`", func() testCase { + // when + keyPair, err := NewSelfSignedCert(func() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(0), + BasicConstraintsValid: true, + IsCA: true, + } + }) + // then + Expect(err).ToNot(HaveOccurred()) + + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage)" +`, + input: *keyPair, + } + }), + Entry("certificate with key usage extension `digitalSignature`", func() testCase { + // when + keyPair, err := NewSelfSignedCert(func() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(0), + BasicConstraintsValid: true, + IsCA: true, + KeyUsage: x509.KeyUsageCertSign | + x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, + } + }) + // then + Expect(err).ToNot(HaveOccurred()) + + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "key usage extension 'digitalSignature' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)" +`, + input: *keyPair, + } + }), + Entry("certificate with key usage extension `keyAgreement`", func() testCase { + // when + keyPair, err := NewSelfSignedCert(func() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(0), + BasicConstraintsValid: true, + IsCA: true, + KeyUsage: x509.KeyUsageCertSign | + x509.KeyUsageCRLSign | x509.KeyUsageKeyAgreement, + } + }) + // then + Expect(err).ToNot(HaveOccurred()) + + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "key usage extension 'keyAgreement' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)" +`, + input: *keyPair, + } + }), + Entry("certificate with key usage extension `keyEncipherment`", func() testCase { + // when + keyPair, err := NewSelfSignedCert(func() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(0), + BasicConstraintsValid: true, + IsCA: true, + KeyUsage: x509.KeyUsageCertSign | + x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment, + } + }) + // then + Expect(err).ToNot(HaveOccurred()) + + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "key usage extension 'keyEncipherment' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)" +`, + input: *keyPair, + } + }), + Entry("certificate with multiple violations", func() testCase { + // when + keyPair, err := NewSelfSignedCert(func() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(0), + IsCA: false, + KeyUsage: x509.KeyUsageDigitalSignature | + x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment, + } + }) + // then + Expect(err).ToNot(HaveOccurred()) + + return testCase{ + expectedErr: ` + violations: + - field: cert + message: "basic constraint 'CA' must be set to 'true' (see X509-SVID: 4.1. Basic Constraints)" + - field: cert + message: "key usage extension 'keyCertSign' must be set (see X509-SVID: 4.3. Key Usage)" + - field: cert + message: "key usage extension 'digitalSignature' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)" + - field: cert + message: "key usage extension 'keyAgreement' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)" + - field: cert + message: "key usage extension 'keyEncipherment' must NOT be set (see X509-SVID: Appendix A. X.509 Field Reference)" +`, + input: *keyPair, + } + }), + ) +}) diff --git a/pkg/core/ca/provided/manager.go b/pkg/core/ca/provided/manager.go index c9893ffe76e0..b16d1ea9516b 100644 --- a/pkg/core/ca/provided/manager.go +++ b/pkg/core/ca/provided/manager.go @@ -51,6 +51,10 @@ func NewProvidedCaManager(secretManager secret_manager.SecretManager) ProvidedCa } func (p *providedCaManager) AddSigningCert(ctx context.Context, mesh string, signingPair tls.KeyPair) (*SigningCert, error) { + if err := ValidateCaCert(signingPair); err != nil { + return nil, err + } + providedCaSecret := &core_system.SecretResource{} if err := p.secretManager.Get(ctx, providedCaSecret, core_store.GetBy(providedCaSecretKey(mesh))); err != nil { if core_store.IsResourceNotFound(err) { diff --git a/pkg/core/ca/provided/manager_test.go b/pkg/core/ca/provided/manager_test.go index c850f32eabeb..96b3d415aab3 100644 --- a/pkg/core/ca/provided/manager_test.go +++ b/pkg/core/ca/provided/manager_test.go @@ -3,13 +3,13 @@ package provided_test import ( "context" + builtin_issuer "github.com/Kong/kuma/pkg/core/ca/builtin/issuer" "github.com/Kong/kuma/pkg/core/ca/provided" core_store "github.com/Kong/kuma/pkg/core/resources/store" "github.com/Kong/kuma/pkg/core/secrets/cipher" "github.com/Kong/kuma/pkg/core/secrets/manager" "github.com/Kong/kuma/pkg/core/secrets/store" "github.com/Kong/kuma/pkg/plugins/resources/memory" - "github.com/Kong/kuma/pkg/tls" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -33,12 +33,12 @@ var _ = Describe("CA Provided Manager", func() { Expect(core_store.IsResourceNotFound(err)).To(BeTrue()) // when - pair := tls.KeyPair{ - CertPEM: []byte("CERT"), - KeyPEM: []byte("KEY"), - } - signingCert, err := caManager.AddSigningCert(context.Background(), meshName, pair) + signingPair, err := builtin_issuer.NewRootCA(meshName) + // then + Expect(err).ToNot(HaveOccurred()) + // when + signingCert, err := caManager.AddSigningCert(context.Background(), meshName, *signingPair) // then Expect(err).ToNot(HaveOccurred()) @@ -53,21 +53,22 @@ var _ = Describe("CA Provided Manager", func() { It("should not allow to add another signing cert to existing provided CA", func() { // setup CA with CA Root - caRoot := tls.KeyPair{ - CertPEM: []byte("CERT"), - KeyPEM: []byte("KEY"), - } - _, err := caManager.AddSigningCert(context.Background(), meshName, caRoot) + + // when + signingPair, err := builtin_issuer.NewRootCA(meshName) + // then + Expect(err).ToNot(HaveOccurred()) + + _, err = caManager.AddSigningCert(context.Background(), meshName, *signingPair) Expect(err).ToNot(HaveOccurred()) - // given - newRoot := tls.KeyPair{ - CertPEM: []byte("CERT2"), - KeyPEM: []byte("KEY2"), - } + // when + newSigningPair, err := builtin_issuer.NewRootCA(meshName) + // then + Expect(err).ToNot(HaveOccurred()) // when - _, err = caManager.AddSigningCert(context.Background(), meshName, newRoot) + _, err = caManager.AddSigningCert(context.Background(), meshName, *newSigningPair) // then Expect(err).To(HaveOccurred()) @@ -78,11 +79,13 @@ var _ = Describe("CA Provided Manager", func() { Describe("DeleteSigningCert", func() { BeforeEach(func() { // setup CA with CA Root - caRoot := tls.KeyPair{ - CertPEM: []byte("CERT"), - KeyPEM: []byte("KEY"), - } - _, err := caManager.AddSigningCert(context.Background(), meshName, caRoot) + + // when + signingPair, err := builtin_issuer.NewRootCA(meshName) + // then + Expect(err).ToNot(HaveOccurred()) + + _, err = caManager.AddSigningCert(context.Background(), meshName, *signingPair) Expect(err).ToNot(HaveOccurred()) }) @@ -130,11 +133,13 @@ var _ = Describe("CA Provided Manager", func() { Describe("DeleteCa", func() { It("should delete CA", func() { // setup CA with CA Root - caRoot := tls.KeyPair{ - CertPEM: []byte("CERT"), - KeyPEM: []byte("KEY"), - } - _, err := caManager.AddSigningCert(context.Background(), meshName, caRoot) + + // when + signingPair, err := builtin_issuer.NewRootCA(meshName) + // then + Expect(err).ToNot(HaveOccurred()) + + _, err = caManager.AddSigningCert(context.Background(), meshName, *signingPair) Expect(err).ToNot(HaveOccurred()) // when @@ -164,9 +169,15 @@ var _ = Describe("CA Provided Manager", func() { Describe("GenerateWorkloadCert", func() { BeforeEach(func() { // setup CA with CA Root - pair, err := tls.NewSelfSignedCert("kuma", tls.ServerCertType) + + // when + signingPair, err := builtin_issuer.NewRootCA(meshName) + // then Expect(err).ToNot(HaveOccurred()) - _, err = caManager.AddSigningCert(context.Background(), meshName, pair) + + // when + _, err = caManager.AddSigningCert(context.Background(), meshName, *signingPair) + // then Expect(err).ToNot(HaveOccurred()) }) diff --git a/pkg/core/ca/provided/rest/webservice_test.go b/pkg/core/ca/provided/rest/webservice_test.go index 49fdf6945166..4a49972b0c2e 100644 --- a/pkg/core/ca/provided/rest/webservice_test.go +++ b/pkg/core/ca/provided/rest/webservice_test.go @@ -129,6 +129,77 @@ var _ = Describe("Provided CA WS", func() { }, })) }) + + It("should not allow to add improper CA certificate", func() { + // when + pair.CertPEM = []byte(` +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAwMQ0wCwYDVQQKEwRLdW1h +MQ0wCwYDVQQLEwRNZXNoMRAwDgYDVQQDEwdkZWZhdWx0MB4XDTIwMDEyOTE2MDgw +NFoXDTMwMDEyNjE2MDgxNFowQDENMAsGA1UEChMES3VtYTEdMAsGA1UECxMETWVz +aDAOBgNVBAsTB2xldmVsLTExEDAOBgNVBAMTB2RlZmF1bHQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC+HgOFAO4T+heMQnO3VOaCYzqzkO2whggQ+yzF +0OIMSOYnOEYA93QGYB+vPf99dnoKqqp8C9MqRMMaNI3SEa23W2EXvbB41+ZBf1zW +BS6QQDSARFI6xJaXOnwDNzl+Ne2XmOJCS9pqk2hDw+WMtzxISyCd293EPW0NVY3q +H3RsyC26rIozsWf/X6fCFETEpFM5KVHSJL1511Y9w3cc0auXe+MIXShQX/v36ckm +p5pdrC3T7Y8IyTFjpPrLMVpL+HQ7LeZcs5DUfmfdxnYxIQwpBKz19/yDN4elbJem +AHVcQo1xS+KtUp9on9YTm/SuJ4bz0H9idBN25CevCY9W7CbjAgMBAAGjQDA+MA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MBsGA1UdEQQUMBKGEHNwaWZm +ZTovL2RlZmF1bHQwDQYJKoZIhvcNAQELBQADggEBACVXnYWCCrji551pbJsOCGYJ +GEqlvcwNnnYdykas4GrfsbW2rglmaXv0uG8iH2sAH+4/MjGjnlQ6Y6Fj7mDFnidj +ugU964sEDnLuU0CtaIpHl7VZ13I0EzmfY+GsCrcIXIxbAxwWTJhz77XqbHe3baLx +Sh9wHgz/aZuy99rq9OoAvUALEaIfxrvUsVs25jLuv0Xzy57B2Dpqo0odshDA4WSS +MynQnSX7aFg1jqZQL4YjPHryEQQRj8mgjqiWp8M4/PHq5s09zDMB0DCag0QtdC/k +ydtqRoojiRS2fXY8DhFRqqRVBqLvA+7eTEKpzfjUTyEovMqxIM2n4U5MSGKQlbM= +-----END CERTIFICATE----- + +`) + pair.KeyPEM = []byte(` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA2F7A2cdkjR/7nm+yb6IZ6P00BgW0JvYU3MUdmUZy7QXTthID +Kis8m4z7pXcEVxjYiRYcvxY6ig2YnRf/LMF3hFKtvdNpwYq1oNrJGNOjwk5bs2WH +5a/BHXwoeMJBcOOWqSZ8TF1ghRmM6ok6G72c7n1+rPRIkW+jZYqSQ/sJpMtBe3Wn +f4ebVejlYJdl8PtbntiE0FlkuWOKn83ykMvtvZWSF763Fu/8+pWsNEwajJEGc80B +9LtI+hxFtwRL22HYJ6w274jCW27iAkE5BunJ0EiMm+vXOvH/PFvEz4RupsavqDCq +4a8pPJcerbS7kITi1bLcYpKHksRUi09LTrp3lQIDAQABAoIBADUU2d8TqbltlT9D +S9VTQWQFalPn5lCAopGe0ioePGelvFC4jooz3USUC9CGKExtzgGjqR3ACFCCEWTI +1FNYi0etOO6PBSz0KKbzxc4PbednbdvPFs3klk3zfcJSddeKHhYVWP0rE1jT8dxA +Gj9f/zYLF566t2rmpoFsw4Fl/vGscBzOq777oOPNviGPA+MSXOeI+xbMGRq+fU3R +RoMaHzjokCibQVWZb0FaLpkYGCNz7P1Zvhpkt0OZrB//e5oNjX7ksTpJsQmffCMl +XG/wX34KErjKD4CiNL5Y2CtfOKNstjS7yZoM81BIs/1dQ6OA9VGthHn6Qh8hXn4B +Y0M7wCkCgYEA97yDytk7rF6vtC3vYSKOkuYY+X06h+avDOUGB3hTYLd1vcNkA0hF +FFigof349yYG3JUdXcPPehtMzNWn1zOKfXnjHrnd/RHctuhIkKeX4AtI7+MFMT2n +vXttwBcYgRXo0+isGnSysBdHI/sF/VMLcszy5eUfy0EKxFBemufaPpMCgYEA35Zk +2DJkQ88nEtRADfHExYHJNxEmsq3V1PKQT+j68zKE84zzp1emyYH+d3ur7wXr86ZX +UA4aDVhAXcD7NUq9mVecUaRYRYmBxap1fuvu+wXXYVFsxhwWraKGSrYzwPDrt3xb +eqWAetiAqmJp8UZpyev7EHOYsWf+EUZwJLiKojcCgYBpZZCEeotCuD30YB6ZqsQR +h0dUzYxbSS9sQvufrfd7DFJRW5FvPA33rAUbJhwHuevtaJtHywi4IGk6NCPmEI14 ++KRB7D2fbzwBrS1CLasVrHdpZ6JL4rk8igiVUr4gHRwjG7gswT1MYXroueFAd1ZF +jyA/4oz2QkO8ZZz6Nm3JdQKBgCIH6wt5CAfGJOVZxvIYZWHGclDeXGx/xvclgE+Z +X3DatJ+5SXCkB6/OCGQ5P58e4J3yKIH304FKeGmMsO+Yk6keS52ljQXwev8SBdYu +pO4yImkekpbIua7t+NCwUMpCIS6JUAcn35lTEKpeVk+x7vIb59fGMGx4LpSEixcb +u4YbAoGBAIgfmzZ3SCLx4pBC5/o/LdVMpzfV3vPzvu5dIsQDOat70aTHmDl+S6Cq +K96xPXFnxPS0a5TLvVCcGnA39iZDgaIWCYEEmRRsnYhlKzkNgqJEB8ZZfENwFBuO +1kXKbhap66yPSayVOAfyVS4ACia8BwT+x64AFSKjaudVNX+rGatX +-----END RSA PRIVATE KEY----- +`) + _, err := client.AddSigningCertificate("demo", pair) + + // then + Expect(err).To(HaveOccurred()) + apiErr := err.(*types.Error) + + Expect(*apiErr).To(Equal(types.Error{ + Title: "Could not add signing cert", + Details: "Resource is not valid", + Causes: []types.Cause{ + { + Field: ".", + Message: "not a valid TLS key pair: tls: private key does not match public key", + }, + }, + })) + }) }) Describe("Get certificates", func() { diff --git a/pkg/core/managers/apis/mesh/mesh_manager_test.go b/pkg/core/managers/apis/mesh/mesh_manager_test.go index 34c1507039a7..801d6f0a0e20 100644 --- a/pkg/core/managers/apis/mesh/mesh_manager_test.go +++ b/pkg/core/managers/apis/mesh/mesh_manager_test.go @@ -9,6 +9,7 @@ import ( mesh_proto "github.com/Kong/kuma/api/mesh/v1alpha1" "github.com/Kong/kuma/pkg/core/ca/builtin" + builtin_issuer "github.com/Kong/kuma/pkg/core/ca/builtin/issuer" "github.com/Kong/kuma/pkg/core/ca/provided" core_mesh "github.com/Kong/kuma/pkg/core/resources/apis/mesh" "github.com/Kong/kuma/pkg/core/resources/manager" @@ -20,7 +21,6 @@ import ( "github.com/Kong/kuma/pkg/core/validators" "github.com/Kong/kuma/pkg/plugins/resources/memory" test_resources "github.com/Kong/kuma/pkg/test/resources" - "github.com/Kong/kuma/pkg/tls" util_proto "github.com/Kong/kuma/pkg/util/proto" ) @@ -42,10 +42,16 @@ var _ = Describe("Mesh Manager", func() { }) createProvidedCa := func(meshName string) string { - pair, err := tls.NewSelfSignedCert("kuma", tls.ServerCertType) + // when + signingPair, err := builtin_issuer.NewRootCA(meshName) + // then Expect(err).ToNot(HaveOccurred()) - signingCert, err := providedCaManager.AddSigningCert(context.Background(), meshName, pair) + + // when + signingCert, err := providedCaManager.AddSigningCert(context.Background(), meshName, *signingPair) + // then Expect(err).ToNot(HaveOccurred()) + return signingCert.Id } @@ -242,9 +248,15 @@ var _ = Describe("Mesh Manager", func() { Describe("Update()", func() { It("should not allow to change CA when mTLS is enabled", func() { // setup - pair, err := tls.NewSelfSignedCert("kuma", tls.ServerCertType) + + // when + signingPair, err := builtin_issuer.NewRootCA("mesh-1") + // then Expect(err).ToNot(HaveOccurred()) - _, err = providedCaManager.AddSigningCert(context.Background(), "mesh-1", pair) + + // when + _, err = providedCaManager.AddSigningCert(context.Background(), "mesh-1", *signingPair) + // then Expect(err).ToNot(HaveOccurred()) // given diff --git a/pkg/tls/cert.go b/pkg/tls/cert.go index 11a12b3fc4c3..2ce50d9c24ba 100644 --- a/pkg/tls/cert.go +++ b/pkg/tls/cert.go @@ -20,11 +20,6 @@ var ( DefaultValidityPeriod = 10 * 365 * 24 * time.Hour ) -type KeyPair struct { - CertPEM []byte - KeyPEM []byte -} - type CertType string const ( diff --git a/pkg/tls/keypair.go b/pkg/tls/keypair.go new file mode 100644 index 000000000000..15ca83bd75a3 --- /dev/null +++ b/pkg/tls/keypair.go @@ -0,0 +1,53 @@ +package tls + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + + "github.com/pkg/errors" +) + +type KeyPair struct { + CertPEM []byte + KeyPEM []byte +} + +func ToKeyPair(key interface{}, cert []byte) (*KeyPair, error) { + keyPem, err := pemEncodeKey(key) + if err != nil { + return nil, errors.Wrap(err, "failed to PEM encode a private key") + } + certPem, err := pemEncodeCert(cert) + if err != nil { + return nil, errors.Wrap(err, "failed to PEM encode a certificate") + } + return &KeyPair{ + CertPEM: certPem, + KeyPEM: keyPem, + }, nil +} + +func pemEncodeKey(priv interface{}) ([]byte, error) { + var block *pem.Block + switch k := priv.(type) { + case *rsa.PrivateKey: + block = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + default: + return nil, errors.Errorf("unsupported private key type %T", priv) + } + var keyBuf bytes.Buffer + if err := pem.Encode(&keyBuf, block); err != nil { + return nil, err + } + return keyBuf.Bytes(), nil +} + +func pemEncodeCert(derBytes []byte) ([]byte, error) { + var certBuf bytes.Buffer + if err := pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return nil, err + } + return certBuf.Bytes(), nil +}