diff --git a/pkg/tlsutil/tls.go b/pkg/tlsutil/tls.go index c2cb284a6b1..5480cf1db64 100644 --- a/pkg/tlsutil/tls.go +++ b/pkg/tlsutil/tls.go @@ -152,7 +152,8 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service if err != nil { return nil, nil, nil, err } - appSecret, err := getAppSecretInCluster(scg.KubeClient, ToAppSecretName(k, n, config.CertName), ns) + appSecretName := ToAppSecretName(k, n, config.CertName) + appSecret, err := getAppSecretInCluster(scg.KubeClient, appSecretName, ns) if err != nil { return nil, nil, nil, err } @@ -169,9 +170,29 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service } else if hasAppSecret && !hasCASecretAndConfigMap { return nil, nil, nil, ErrCANotFound } else if !hasAppSecret && hasCASecretAndConfigMap { - // TODO + caKey, err := parsePEMEncodedPrivateKey(caSecret.Data[TLSPrivateCAKeyKey]) + if err != nil { + return nil, nil, nil, err + } + caCert, err := parsePEMEncodedCert([]byte(caConfigMap.Data[TLSCACertKey])) + if err != nil { + return nil, nil, nil, err + } + key, err := newPrivateKey() + if err != nil { + return nil, nil, nil, err + } + cert, err := newSignedCertificate(config, service, key, caCert, caKey) + if err != nil { + return nil, nil, nil, err + } + appSecret, err := scg.KubeClient.CoreV1().Secrets(ns).Create(toTLSSecret(key, cert, appSecretName, ns)) + if err != nil { + return nil, nil, nil, err + } + return appSecret, caConfigMap, caSecret, nil } else { - // TODO + // TODO: handle the case where both CA and Application TLS assets don't exist. } return nil, nil, nil, nil } diff --git a/test/e2e/testdata/ca-csr.json b/test/e2e/testdata/ca-csr.json new file mode 100644 index 00000000000..6f13ed8d334 --- /dev/null +++ b/test/e2e/testdata/ca-csr.json @@ -0,0 +1,17 @@ +{ + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "L": "San Francisco", + "ST": "California", + "C": "USA" + } + ], + "CN": "ca", + "ca": { + "expiry": "87600h" + } + } diff --git a/test/e2e/testdata/ca.crt b/test/e2e/testdata/ca.crt new file mode 100644 index 00000000000..015172df5dc --- /dev/null +++ b/test/e2e/testdata/ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhDCCAmygAwIBAgIUJCpEclRVqfgYKUIyrk6m46BqH0EwDQYJKoZIhvcNAQEL +BQAwSDEMMAoGA1UEBhMDVVNBMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMQswCQYDVQQDEwJjYTAeFw0xODA4MzAyMDEyMDBaFw0y +ODA4MjcyMDEyMDBaMEgxDDAKBgNVBAYTA1VTQTETMBEGA1UECBMKQ2FsaWZvcm5p +YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzELMAkGA1UEAxMCY2EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDwSnTyS56xvTRBNbkNNoYBfOJNGzSNCtF/ +u2NrYUQW9YOCCceuhl8q4g8H6/+HTV9RlRiTAY7DSFSZUK/C3x4uFhY1emXjzgqT +Z19FKEKwkVhqo7XSkGrqb37U0vgdO2eyGWqVt2gS50wNimo4Z3HcfsziDtqFZpxb +9ZuCiWmGpnkx+NuH9Qq2LBHSdOHI+HWw7E/91ZAaTmW/QA9W9HvxKNC9pmFFBRtd +WDjGvHsTmpgPZ2Pce/jcJ6SAaO82KXM0ksW4LmaK4OTUPN+c3KODA/gSau77DNNG +bnjQ6CkyKKOfInGDVhpG57p+LuIbt724bCxmNRkvjYwrr+dEL4SpAgMBAAGjZjBk +MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1UdDgQWBBSN +6FinnI4qg4IeK9PGzzufD6usjDAfBgNVHSMEGDAWgBSN6FinnI4qg4IeK9PGzzuf +D6usjDANBgkqhkiG9w0BAQsFAAOCAQEAy6sn3xyNcQ/HzD1Ii7p4toc5qbgDONnq +9YkbIoNFxFj8DTQ86r6jcj94PnylIhBe1I1k70tVVal7nM+4wnNaTktAfiQN/mYJ +RyvMTXN6+Vsl93AeBMo7DIGElz2sL2EkOTct1QmTr7hr/3u4SfBvppFnxYqJKiI3 +GX3V0kV1iuAllyHR787hkWq28LQ3WXtqnybAR23SMVtQNrHw1t1ia5eStK4Gbfl/ +FN/BNwkV6i8Q/5B2obCUJWpzt4UqB4hXv3TmhYCeA8ddq7LYjjil11Ed21o95QyK +FooF2jEmda+zivmB/XKC+5+DfL00x0G1QqbME6ilGkpRx2gDFg03cg== +-----END CERTIFICATE----- diff --git a/test/e2e/testdata/ca.csr b/test/e2e/testdata/ca.csr new file mode 100644 index 00000000000..0a1c4abe607 --- /dev/null +++ b/test/e2e/testdata/ca.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICjTCCAXUCAQAwSDEMMAoGA1UEBhMDVVNBMRMwEQYDVQQIEwpDYWxpZm9ybmlh +MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMQswCQYDVQQDEwJjYTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAPBKdPJLnrG9NEE1uQ02hgF84k0bNI0K0X+7 +Y2thRBb1g4IJx66GXyriDwfr/4dNX1GVGJMBjsNIVJlQr8LfHi4WFjV6ZePOCpNn +X0UoQrCRWGqjtdKQaupvftTS+B07Z7IZapW3aBLnTA2Kajhncdx+zOIO2oVmnFv1 +m4KJaYameTH424f1CrYsEdJ04cj4dbDsT/3VkBpOZb9AD1b0e/Eo0L2mYUUFG11Y +OMa8exOamA9nY9x7+NwnpIBo7zYpczSSxbguZorg5NQ835zco4MD+BJq7vsM00Zu +eNDoKTIoo58icYNWGkbnun4u4hu3vbhsLGY1GS+NjCuv50QvhKkCAwEAAaAAMA0G +CSqGSIb3DQEBCwUAA4IBAQDDqdtWrOmptqpQNDG6nt1EW6KLwSPhZBx+wwGVpPtb +cXVSvjQkmIzK0G22XtDnIIix+D65hvFIPyVvKYVhDm5LhRvcguyRAV2SkrDlhBka +tZG03yvvUE4hbdWjJtk7CAUoKOZ1Sl47zdf0Rn1b/LZd9gQ6Ew08YRfdZ9VLQdOm +j/o6owGqIpU/dCaMZZ/8jzccmqt6QkiTSlyrA2ws38S0wcEsILp8vppLQc056Qiw +AcqwwUw1jp8omqozPCKir12gNjkWxLnZ56ka1PwUJr0cj8QYUqHC0q/xl2eNw2lU +W8qZn+2N43F3KvJ5BPUbuu+69G8EQhhMKHq69G6WSGhQ +-----END CERTIFICATE REQUEST----- diff --git a/test/e2e/testdata/ca.key b/test/e2e/testdata/ca.key new file mode 100644 index 00000000000..d170af1699e --- /dev/null +++ b/test/e2e/testdata/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA8Ep08kuesb00QTW5DTaGAXziTRs0jQrRf7tja2FEFvWDggnH +roZfKuIPB+v/h01fUZUYkwGOw0hUmVCvwt8eLhYWNXpl484Kk2dfRShCsJFYaqO1 +0pBq6m9+1NL4HTtnshlqlbdoEudMDYpqOGdx3H7M4g7ahWacW/WbgolphqZ5Mfjb +h/UKtiwR0nThyPh1sOxP/dWQGk5lv0APVvR78SjQvaZhRQUbXVg4xrx7E5qYD2dj +3Hv43CekgGjvNilzNJLFuC5miuDk1DzfnNyjgwP4Emru+wzTRm540OgpMiijnyJx +g1YaRue6fi7iG7e9uGwsZjUZL42MK6/nRC+EqQIDAQABAoIBAAbaSL2ENJFjEPNv +IcjjriyqsBV82iHPlivrXyl3y6ZP+CEkQEKU6G/jpIQYUeA876P2+Y1vtO+Sx37b +0zdef5DW5mk+BVvay2hqwUfKnyRD8N6RrqTDo5jt9xMAtTy4LfvhR63fXiNz3zJf +qSnUoWWlZBhqTgcR5xGkTnwJiS3i0Vk9/xU1zNL7OZpSSSuefXuq2qQtG+Gayrps +KAp1essnKnNRyiQH2yOn3pXs5Mj+ytWCV7C9eL1TAg0ZSIjuV+S/CNpCHsVLv5p6 +yj/CWjzV7NOhsdHoo5t1FsALhQaov0bb6hv96ZbTwkPoegr1SLQnYx63kOo//AmK +uCgZgxECgYEA/qmFoBlRNm2AJ/vRp0t6JoERF9LhsUWSYRmcSho2xG5fLwzSxCjR +YOxFEdZl3hzkH/rhY7+Vg6rOH5rhjL4hBKnrCkZcC88J7WCTI4mJDwTP/iViahAg +RVEvJ6T51qI8N1wojumuQIhbUmVcWiZA49QRC/5DqIgZ1lRDWycqTeUCgYEA8Y2b +mCI5zvjPci/1WBbPqA9ZDdbi4MmvO/RG+ik4RZnN7poxg2JU/Ta5RAFB1UY21bo8 +JTiL2pRqLaGLi0HPEJEaS6K5a+9oK5tUtmJp6CgpnNiNmALUyaoXbg3lrv5ajo74 +G94AZEIha0r9ReECvtk/sdsK1IgZaBtCIJ2Lj3UCgYEAjo+4DngdzqpeJAQEyfKm +3wdB2mRjlCmuWE1OAO3L2wsundg/5TA0hl2+DM5JGJ5z1rNLmduWh68G1QqPWYrW +URYOTiI1RScSF6EIvcwwvgejqFKlVVrRtfxMuZTRiCYqL5OX4OlQcy/ib63ulUj0 +6pW9NUmR9ra6QBHL4yt5s0ECgYEAsAJpX/+AdAnkzuWXNqrYgTM9xtHP28/aOiuT +FHG4qS6bWcNNVNjv6NpZQO5RlCBnkHD1poF/lrQSclGGJuC7Cu1QZdCan8WA+FVk +8sjfNuUc/UbmVd+qQZAJJo5F0K9SORKAQ34Odv+g7ldkGekNYRdYTDa5u4e4S52h +H7bsnIkCgYAJZRkDWjhPhWX7hv9HcLcyW/yOe9tEys/2UoYBr+j3X0eraKR1+EUr +Xli+yuPBbRo5+bEzAXI/gbZGu3J6vZm0J9qHc1ybqLvcTcOweTBU7xs4aeJsSK5O +w0BnNOc/05hLf2Ow02ZlZBELMXpp1Y187+FKEmkwxTrgaGB3rZanVA== +-----END RSA PRIVATE KEY----- diff --git a/test/e2e/testdata/gencert.json b/test/e2e/testdata/gencert.json new file mode 100644 index 00000000000..dcf31054818 --- /dev/null +++ b/test/e2e/testdata/gencert.json @@ -0,0 +1,13 @@ +{ + "signing": { + "default": { + "usages": [ + "signing", + "key encipherment", + "server auth", + "client auth" + ], + "expiry": "87600h" + } + } +} diff --git a/test/e2e/testdata/gencert.sh b/test/e2e/testdata/gencert.sh new file mode 100755 index 00000000000..c81225618da --- /dev/null +++ b/test/e2e/testdata/gencert.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if ! [[ "$0" =~ "./gencert.sh" ]]; then + echo "must be run from 'testdata'" + exit 255 +fi + +if ! which cfssl; then + echo "cfssl is not installed" + exit 255 +fi + +cfssl gencert --initca=true ./ca-csr.json | cfssljson --bare ca - +mv ca.pem ca.crt +mv ca-key.pem ca.key +if which openssl >/dev/null; then + openssl x509 -in ca.crt -noout -text +fi diff --git a/test/e2e/tls_util_test.go b/test/e2e/tls_util_test.go index 8d9584b8e0f..4490e258dcf 100644 --- a/test/e2e/tls_util_test.go +++ b/test/e2e/tls_util_test.go @@ -15,6 +15,7 @@ package e2e import ( + "io/ioutil" "reflect" "testing" @@ -27,32 +28,53 @@ import ( var ( // TLS test variables. - crKind = "Pod" - crName = "example-pod" - certName = "app-cert" - + crKind = "Pod" + crName = "example-pod" + certName = "app-cert" caConfigMapAndSecretName = tlsutil.ToCASecretAndConfigMapName(crKind, crName) - caConfigMap = &v1.ConfigMap{ + appSecretName = tlsutil.ToAppSecretName(crKind, crName, certName) + + caConfigMap *v1.ConfigMap + caSecret *v1.Secret + appSecret *v1.Secret + + ccfg *tlsutil.CertConfig +) + +// setup test variables. +func init() { + caCertBytes, err := ioutil.ReadFile("./testdata/ca.crt") + if err != nil { + panic(err) + } + caConfigMap = &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: caConfigMapAndSecretName, }, + Data: map[string]string{tlsutil.TLSCACertKey: string(caCertBytes)}, + } + + caKeyBytes, err := ioutil.ReadFile("./testdata/ca.key") + if err != nil { + panic(err) } caSecret = &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: caConfigMapAndSecretName, }, + Data: map[string][]byte{tlsutil.TLSPrivateCAKeyKey: caKeyBytes}, } appSecret = &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: tlsutil.ToAppSecretName(crKind, crName, certName), + Name: appSecretName, }, } ccfg = &tlsutil.CertConfig{ CertName: certName, } -) +} // TestBothAppAndCATLSAssetsExist ensures that when both application // and CA TLS assets exist in the k8s cluster for a given cr, @@ -142,3 +164,73 @@ func TestOnlyAppSecretExist(t *testing.T) { t.Fatalf("expect %v, but got %v", tlsutil.ErrCANotFound.Error(), err.Error()) } } + +// TestOnlyCAExist ensures that at the case where only the CA exists in the cluster; +// GenerateCert can retrieve the CA and uses it to create a new application secret. +func TestOnlyCAExist(t *testing.T) { + f := framework.Global + ctx := f.NewTestCtx(t) + defer ctx.Cleanup(t) + namespace, err := ctx.GetNamespace() + if err != nil { + t.Fatal(err) + } + + _, err = f.KubeClient.CoreV1().ConfigMaps(namespace).Create(caConfigMap) + if err != nil { + t.Fatal(err) + } + _, err = f.KubeClient.CoreV1().Secrets(namespace).Create(caSecret) + if err != nil { + t.Fatal(err) + } + + cg := tlsutil.NewSDKCertGenerator(f.KubeClient) + // Use Pod as a dummy runtime object for the CR input of GenerateCert(). + mCR := &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: crKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + Namespace: namespace, + }, + } + appSvc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-service", + Namespace: namespace, + }, + } + appSecret, _, _, err := cg.GenerateCert(mCR, appSvc, ccfg) + if err != nil { + t.Fatal(err) + } + + // check if appSecret has the correct fields. + if appSecretName != appSecret.Name { + t.Fatalf("expect the secret name %v, but got %v", appSecretName, appSecret.Name) + } + if namespace != appSecret.Namespace { + t.Fatalf("expect the secret namespace %v, but got %v", namespace, appSecret.Namespace) + } + if v1.SecretTypeTLS != appSecret.Type { + t.Fatalf("expect the secret type %v, but got %v", v1.SecretTypeTLS, appSecret.Type) + } + if _, ok := appSecret.Data[v1.TLSCertKey]; !ok { + t.Fatalf("expect the secret to have the data field %v, but got none", v1.TLSCertKey) + } + if _, ok := appSecret.Data[v1.TLSPrivateKeyKey]; !ok { + t.Fatalf("expect the secret to have the data field %v, but got none", v1.TLSPrivateKeyKey) + } + + // check if appSecret exists in k8s cluster. + appSecretFromCluster, err := f.KubeClient.CoreV1().Secrets(namespace).Get(appSecretName, metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + // check if appSecret returned from GenerateCert is the same as the one that exists in the k8s. + if !reflect.DeepEqual(appSecret, appSecretFromCluster) { + t.Fatalf("expect %+v, but got %+v", appSecret, appSecretFromCluster) + } +}