Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

e2e: backend TLS policy #2853

Merged
merged 8 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"flag"
"testing"

"github.com/envoyproxy/gateway/test/e2e/utils/certificate"

"github.com/stretchr/testify/require"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
Expand Down Expand Up @@ -54,6 +56,14 @@ func TestE2E(t *testing.T) {
})

cSuite.Setup(t)
egSetup(t, cSuite)
t.Logf("Running %d E2E tests", len(tests.ConformanceTests))
cSuite.Run(t, tests.ConformanceTests)
}

// set up additional resources that are created and cleaned up programmatically like certificates
func egSetup(t *testing.T, testSuite *suite.ConformanceTestSuite) {
zirain marked this conversation as resolved.
Show resolved Hide resolved
secret, configmap := certificate.MustCreateSelfSignedCAConfigmapAndCertSecret(t, "gateway-conformance-infra", "backend-tls-checks-certificate", []string{"example.com"})
testSuite.Applier.MustApplyObjectsWithCleanup(t, testSuite.Client, testSuite.TimeoutConfig, []client.Object{secret}, testSuite.Cleanup)
testSuite.Applier.MustApplyObjectsWithCleanup(t, testSuite.Client, testSuite.TimeoutConfig, []client.Object{configmap}, testSuite.Cleanup)
}
98 changes: 98 additions & 0 deletions test/e2e/testdata/backend-tls.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
apiVersion: v1
kind: Service
metadata:
name: tls-backend-2
namespace: gateway-conformance-infra
spec:
selector:
app: tls-backend-2
ports:
- protocol: TCP
port: 443
targetPort: 8443
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tls-backend-2
namespace: gateway-conformance-infra
labels:
app: tls-backend-2
spec:
replicas: 1
selector:
matchLabels:
app: tls-backend-2
template:
metadata:
labels:
app: tls-backend-2
spec:
containers:
- name: tls-backend
image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
volumeMounts:
- name: secret-volume
mountPath: /etc/secret-volume
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: SERVICE_NAME
value: tls-backend-2
- name: TLS_SERVER_CERT
value: /etc/secret-volume/crt
- name: TLS_SERVER_PRIVKEY
value: /etc/secret-volume/key
resources:
requests:
cpu: 10m
volumes:
- name: secret-volume
secret:
secretName: backend-tls-checks-certificate
items:
- key: tls.crt
path: crt
- key: tls.key
path: key
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: BackendTLSPolicy
metadata:
name: policy-btls
namespace: gateway-conformance-infra
spec:
targetRef:
group: ''
kind: Service
name: tls-backend-2
sectionName: "443"
tls:
caCertRefs:
- name: backend-tls-checks-certificate
group: ''
kind: ConfigMap
hostname: example.com
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-with-backend-tls
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- matches:
- path:
type: PathPrefix
value: /backend-tls
backendRefs:
- name: tls-backend-2
port: 443
48 changes: 48 additions & 0 deletions test/e2e/tests/backend_tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

//go:build e2e
// +build e2e

package tests

import (
"testing"

"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/gateway-api/conformance/utils/http"
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
"sigs.k8s.io/gateway-api/conformance/utils/suite"
)

func init() {
ConformanceTests = append(ConformanceTests, BackendTLSTest)
}

var BackendTLSTest = suite.ConformanceTest{
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
ShortName: "BackendTLS",
Description: "Connect to backend with TLS",
Manifests: []string{"testdata/backend-tls.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("Request to TLS backend", func(t *testing.T) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "http-with-backend-tls", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)

expectedResponse := http.ExpectedResponse{
Request: http.Request{
Path: "/backend-tls",
},
Response: http.Response{
StatusCode: 200,
},
Namespace: ns,
}

http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
})
},
}
168 changes: 168 additions & 0 deletions test/e2e/utils/certificate/certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

//go:build e2e
// +build e2e

// This utility is copied from: https://github.com/kubernetes-sigs/gateway-api/blob/main/conformance/utils/kubernetes/certificate.go
// and adapted to support creation of certificates that are signed with a CA

package certificate

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"testing"
"time"

"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// ensure auth plugins are loaded
_ "k8s.io/client-go/plugin/pkg/client/auth"
)

const (
rsaBits = 2048
validFor = 365 * 24 * time.Hour
)

// MustCreateSelfSignedCAConfigmapAndCertSecret creates a self-signed CA certificate and stores it a configmap
// it also creates an SSL certificate and stores it in a secret
func MustCreateSelfSignedCAConfigmapAndCertSecret(t *testing.T, namespace, secretName string, hosts []string) (*corev1.Secret, *corev1.ConfigMap) {
require.NotEmpty(t, hosts, "require a non-empty hosts for Subject Alternate Name values")

var caCert, serverKey, serverCert bytes.Buffer

require.NoError(t, generateCertAndCA(hosts, &caCert, &serverKey, &serverCert), "failed to generate RSA certificate")

secretData := map[string][]byte{
corev1.TLSCertKey: serverCert.Bytes(),
corev1.TLSPrivateKeyKey: serverKey.Bytes(),
}

newSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: secretName,
},
Type: corev1.SecretTypeTLS,
Data: secretData,
}

configmapData := map[string]string{
"ca.crt": caCert.String(),
}

newConfigmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: secretName,
},
Data: configmapData,
}

return newSecret, newConfigmap
}

// generateCertAndCA generates a basic self signed certificate valid for a year
func generateCertAndCA(hosts []string, caCertOut, keyOut, certOut io.Writer) error {
// first create CA certificate
caPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}

// Create a template for the CA certificate
caTemplate := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "Envoy Gateway CA",
OrganizationalUnit: []string{"Gateway"},
Organization: []string{"EnvoyProxy"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0), // Valid for 1 year
BasicConstraintsValid: true,
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
}

// Create a self-signed CA certificate using the private key and template
caDERBytes, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caPrivateKey.PublicKey, caPrivateKey)
if err != nil {
return err
}

caCert, err := x509.ParseCertificate(caDERBytes)
if err != nil {
return err
}

if err := pem.Encode(caCertOut, &pem.Block{Type: "CERTIFICATE", Bytes: caDERBytes}); err != nil {
return fmt.Errorf("failed creating cert: %w", err)
}

// now create leaf certificate signed with CA's private key
leafPrivateKey, err := rsa.GenerateKey(rand.Reader, rsaBits)
if err != nil {
return fmt.Errorf("failed to generate key: %w", 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: %w", 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,
}

for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, &leafPrivateKey.PublicKey, caPrivateKey)
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}

if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return fmt.Errorf("failed creating cert: %w", err)
}

if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(leafPrivateKey)}); err != nil {
return fmt.Errorf("failed creating key: %w", err)
}

return nil
}
Loading