diff --git a/test/e2e/framework/ssl.go b/test/e2e/framework/ssl.go new file mode 100644 index 000000000..41f0dd0eb --- /dev/null +++ b/test/e2e/framework/ssl.go @@ -0,0 +1,115 @@ +package framework + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io" + "math/big" + "net" + "strings" + "time" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + rsaBits = 2048 + validFor = 365 * 24 * time.Hour +) + +// CreateIngressTLSSecret creates a secret containing TLS certificates for the given Ingress. +// If a secret with the same name already pathExists in the namespace of the +// Ingress, it's updated. +func CreateIngressTLSSecret(client kubernetes.Interface, hosts []string, secreName, namespace string) (host string, rootCA, privKey []byte, err error) { + var k, c bytes.Buffer + host = strings.Join(hosts, ",") + if err = generateRSACerts(host, true, &k, &c); err != nil { + return + } + cert := c.Bytes() + key := k.Bytes() + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secreName, + }, + Data: map[string][]byte{ + v1.TLSCertKey: cert, + v1.TLSPrivateKeyKey: key, + }, + } + var s *v1.Secret + if s, err = client.CoreV1().Secrets(namespace).Get(secreName, metav1.GetOptions{}); err == nil { + s.Data = secret.Data + _, err = client.CoreV1().Secrets(namespace).Update(s) + } else { + _, err = client.CoreV1().Secrets(namespace).Create(secret) + } + return host, cert, key, err +} + +// generateRSACerts generates a basic self signed certificate using a key length +// of rsaBits, valid for validFor time. +func generateRSACerts(host string, isCA bool, keyOut, certOut io.Writer) error { + if len(host) == 0 { + return fmt.Errorf("Require a non-empty host for client hello") + } + priv, err := rsa.GenerateKey(rand.Reader, rsaBits) + if err != nil { + return fmt.Errorf("Failed to generate key: %v", err) + } + notBefore := time.Now() + notAfter := notBefore.Add(validFor) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + + if err != nil { + return fmt.Errorf("failed to generate serial number: %s", err) + } + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "default", + Organization: []string{"Acme Co"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + hosts := strings.Split(host, ",") + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + if isCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return fmt.Errorf("Failed to create certificate: %s", err) + } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return fmt.Errorf("Failed creating cert: %v", err) + } + if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { + return fmt.Errorf("Failed creating keay: %v", err) + } + return nil +} diff --git a/test/e2e/framework/util.go b/test/e2e/framework/util.go index f661e37c9..cbd7f5038 100644 --- a/test/e2e/framework/util.go +++ b/test/e2e/framework/util.go @@ -95,6 +95,7 @@ func LoadConfig(config, context string) (*rest.Config, error) { // RunID unique identifier of the e2e run var RunID = uuid.NewUUID() +// CreateKubeNamespace creates a new namespace in the cluster func CreateKubeNamespace(baseName string, c kubernetes.Interface) (*v1.Namespace, error) { ns := &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -131,6 +132,7 @@ func ExpectNoError(err error, explain ...interface{}) { ExpectWithOffset(1, err).NotTo(HaveOccurred(), explain...) } +// WaitForKubeNamespaceNotExist waits until a namespaces is not present in the cluster func WaitForKubeNamespaceNotExist(c kubernetes.Interface, namespace string) error { return wait.PollImmediate(Poll, time.Minute*2, namespaceNotExist(c, namespace)) } @@ -170,7 +172,7 @@ func noPodsInNamespace(c kubernetes.Interface, namespace string) wait.ConditionF } } -// WaitForPodRunningInNamespace waits default amount of time (PodStartTimeout) for the specified pod to become running. +// WaitForPodRunningInNamespace waits a default amount of time (PodStartTimeout) for the specified pod to become running. // Returns an error if timeout occurs first, or pod goes in to failed state. func WaitForPodRunningInNamespace(c kubernetes.Interface, pod *v1.Pod) error { if pod.Status.Phase == v1.PodRunning { @@ -183,6 +185,72 @@ func waitTimeoutForPodRunningInNamespace(c kubernetes.Interface, podName, namesp return wait.PollImmediate(Poll, defaultTimeout, podRunning(c, podName, namespace)) } +// WaitForSecretInNamespace waits a default amount of time for the specified secret is present in a particular namespace +func WaitForSecretInNamespace(c kubernetes.Interface, namespace, name string) error { + return wait.PollImmediate(1*time.Second, time.Minute*2, secretInNamespace(c, namespace, name)) +} + +func secretInNamespace(c kubernetes.Interface, namespace, name string) wait.ConditionFunc { + return func() (bool, error) { + s, err := c.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return false, err + } + if err != nil { + return false, err + } + + if s != nil { + return true, nil + } + return false, nil + } +} + +// WaitForNoIngressInNamespace waits until there is no ingress object in a particular namespace +func WaitForNoIngressInNamespace(c kubernetes.Interface, namespace, name string) error { + return wait.PollImmediate(1*time.Second, time.Minute*2, noIngressInNamespace(c, namespace, name)) +} + +func noIngressInNamespace(c kubernetes.Interface, namespace, name string) wait.ConditionFunc { + return func() (bool, error) { + ing, err := c.ExtensionsV1beta1().Ingresses(namespace).Get(name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, err + } + + if ing == nil { + return true, nil + } + return false, nil + } +} + +// WaitForIngressInNamespace waits until a particular ingress object exists namespace +func WaitForIngressInNamespace(c kubernetes.Interface, namespace, name string) error { + return wait.PollImmediate(1*time.Second, time.Minute*2, ingressInNamespace(c, namespace, name)) +} + +func ingressInNamespace(c kubernetes.Interface, namespace, name string) wait.ConditionFunc { + return func() (bool, error) { + ing, err := c.ExtensionsV1beta1().Ingresses(namespace).Get(name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return false, err + } + if err != nil { + return false, err + } + + if ing != nil { + return true, nil + } + return false, nil + } +} + func podRunning(c kubernetes.Interface, podName, namespace string) wait.ConditionFunc { return func() (bool, error) { pod, err := c.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{}) @@ -199,12 +267,14 @@ func podRunning(c kubernetes.Interface, podName, namespace string) wait.Conditio } } +// NewInt32 converts int32 to a pointer func NewInt32(val int32) *int32 { p := new(int32) *p = val return p } +// NewInt64 converts int64 to a pointer func NewInt64(val int64) *int64 { p := new(int64) *p = val diff --git a/test/e2e/ssl/secret_update.go b/test/e2e/ssl/secret_update.go index ab540157d..27e28081a 100644 --- a/test/e2e/ssl/secret_update.go +++ b/test/e2e/ssl/secret_update.go @@ -17,16 +17,7 @@ limitations under the License. package ssl import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" - "io" - "math/big" - "net" "strings" "time" @@ -37,15 +28,9 @@ import ( "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes" "k8s.io/ingress-nginx/test/e2e/framework" ) -const ( - rsaBits = 2048 - validFor = 365 * 24 * time.Hour -) - var _ = framework.IngressNginxDescribe("SSL", func() { f := framework.NewDefaultFramework("ssl") @@ -107,7 +92,10 @@ var _ = framework.IngressNginxDescribe("SSL", func() { Expect(err).ToNot(HaveOccurred()) Expect(ing).ToNot(BeNil()) - _, _, _, err = createIngressTLSSecret(f.KubeClientSet, ing) + _, _, _, err = framework.CreateIngressTLSSecret(f.KubeClientSet, + ing.Spec.TLS[0].Hosts, + ing.Spec.TLS[0].SecretName, + ing.Namespace) Expect(err).ToNot(HaveOccurred()) err = f.WaitForNginxServer(host, @@ -130,94 +118,3 @@ var _ = framework.IngressNginxDescribe("SSL", func() { Expect(log).ToNot(ContainSubstring(fmt.Sprintf("error obtaining PEM from secret %v/dummy", f.Namespace.Name))) }) }) - -// createIngressTLSSecret creates a secret containing TLS certificates for the given Ingress. -// If a secret with the same name already pathExists in the namespace of the -// Ingress, it's updated. -func createIngressTLSSecret(kubeClient kubernetes.Interface, ing *v1beta1.Ingress) (host string, rootCA, privKey []byte, err error) { - var k, c bytes.Buffer - tls := ing.Spec.TLS[0] - host = strings.Join(tls.Hosts, ",") - if err = generateRSACerts(host, true, &k, &c); err != nil { - return - } - cert := c.Bytes() - key := k.Bytes() - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: tls.SecretName, - }, - Data: map[string][]byte{ - v1.TLSCertKey: cert, - v1.TLSPrivateKeyKey: key, - }, - } - var s *v1.Secret - if s, err = kubeClient.CoreV1().Secrets(ing.Namespace).Get(tls.SecretName, metav1.GetOptions{}); err == nil { - s.Data = secret.Data - _, err = kubeClient.CoreV1().Secrets(ing.Namespace).Update(s) - } else { - _, err = kubeClient.CoreV1().Secrets(ing.Namespace).Create(secret) - } - return host, cert, key, err -} - -// generateRSACerts generates a basic self signed certificate using a key length -// of rsaBits, valid for validFor time. -func generateRSACerts(host string, isCA bool, keyOut, certOut io.Writer) error { - if len(host) == 0 { - return fmt.Errorf("Require a non-empty host for client hello") - } - priv, err := rsa.GenerateKey(rand.Reader, rsaBits) - if err != nil { - return fmt.Errorf("Failed to generate key: %v", err) - } - notBefore := time.Now() - notAfter := notBefore.Add(validFor) - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - - if err != nil { - return fmt.Errorf("failed to generate serial number: %s", err) - } - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: "default", - Organization: []string{"Acme Co"}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - hosts := strings.Split(host, ",") - for _, h := range hosts { - if ip := net.ParseIP(h); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, h) - } - } - - if isCA { - template.IsCA = true - template.KeyUsage |= x509.KeyUsageCertSign - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - return fmt.Errorf("Failed to create certificate: %s", err) - } - if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return fmt.Errorf("Failed creating cert: %v", err) - } - if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { - return fmt.Errorf("Failed creating keay: %v", err) - } - return nil -}