Skip to content

Commit

Permalink
test: added integration test on HTTPS (#88)
Browse files Browse the repository at this point in the history
* test: added integration test on HTTPS

Signed-off-by: Mattia Lavacca <lavacca.mattia@gmail.com>

---------

Signed-off-by: Mattia Lavacca <lavacca.mattia@gmail.com>
Co-authored-by: Grzegorz Burzyński <czeslavo@gmail.com>
  • Loading branch information
mlavacca and czeslavo authored Apr 22, 2024
1 parent 20ac526 commit 1929b68
Show file tree
Hide file tree
Showing 14 changed files with 476 additions and 177 deletions.
4 changes: 2 additions & 2 deletions pkg/utils/gateway/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
// Gateway Utils - Status Updates
// -----------------------------------------------------------------------------

// IsScheduled indicates whether or not the provided Gateway object was
// IsAccepted indicates whether or not the provided Gateway object was
// marked as scheduled by the controller.
func IsScheduled(gateway *gwtypes.Gateway) bool {
func IsAccepted(gateway *gwtypes.Gateway) bool {
for _, cond := range gateway.Status.Conditions {
if cond.Type == string(gatewayv1.GatewayConditionAccepted) &&
cond.Reason == string(gatewayv1.GatewayClassReasonAccepted) &&
Expand Down
13 changes: 5 additions & 8 deletions pkg/utils/test/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,10 +628,10 @@ func GatewayNotExist(t *testing.T, ctx context.Context, gatewayNSN types.Namespa
}
}

// GatewayIsScheduled returns a function that checks if a Gateway is scheduled.
func GatewayIsScheduled(t *testing.T, ctx context.Context, gatewayNSN types.NamespacedName, clients K8sClients) func() bool {
// GatewayIsAccepted returns a function that checks if a Gateway is scheduled.
func GatewayIsAccepted(t *testing.T, ctx context.Context, gatewayNSN types.NamespacedName, clients K8sClients) func() bool {
return func() bool {
return gatewayutils.IsScheduled(MustGetGateway(t, ctx, gatewayNSN, clients))
return gatewayutils.IsAccepted(MustGetGateway(t, ctx, gatewayNSN, clients))
}
}

Expand Down Expand Up @@ -768,12 +768,9 @@ func GatewayIPAddressExist(t *testing.T, ctx context.Context, gatewayNSN types.N
}

// GetResponseBodyContains issues an HTTP request and checks if a response body contains a string.
func GetResponseBodyContains(t *testing.T, ctx context.Context, clients K8sClients, httpc http.Client, url string, method string, responseContains string) func() bool {
func GetResponseBodyContains(t *testing.T, clients K8sClients, httpc *http.Client, request *http.Request, responseContains string) func() bool {
return func() bool {
req, err := http.NewRequestWithContext(ctx, method, url, nil)
require.NoError(t, err)

resp, err := httpc.Do(req)
resp, err := httpc.Do(request)
if err != nil {
return false
}
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/test_operator_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

testutils "github.com/kong/gateway-operator/pkg/utils/test"
"github.com/kong/gateway-operator/test/helpers"
)

func init() {
Expand Down Expand Up @@ -143,7 +144,7 @@ func TestOperatorLogs(t *testing.T) {
}()

t.Log("deploying a GatewayClass resource")
gatewayClass := testutils.GenerateGatewayClass()
gatewayClass := helpers.MustGenerateGatewayClass(t)
gatewayClass, err = clients.GatewayClient.GatewayV1().GatewayClasses().Create(ctx, gatewayClass, metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(gatewayClass)
Expand All @@ -154,7 +155,7 @@ func TestOperatorLogs(t *testing.T) {
Name: uuid.NewString(),
Namespace: testNamespace.Name,
}
gateway := testutils.GenerateGateway(gatewayNN, gatewayClass)
gateway := helpers.GenerateGateway(gatewayNN, gatewayClass)
gateway, err = clients.GatewayClient.GatewayV1().Gateways(testNamespace.Name).Create(ctx, gateway, metav1.CreateOptions{})
require.NoError(t, err)
cleaner.Add(gateway)
Expand Down
68 changes: 67 additions & 1 deletion test/helpers/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"testing"
"time"

"github.com/stretchr/testify/require"
)

// -----------------------------------------------------------------------------
// Certificate test helper functions and types
// CP-DP mTLS Certificate test helper functions and types
// -----------------------------------------------------------------------------

// Cert represents a TLS certificate that can be used for testing purposes.
Expand Down Expand Up @@ -148,3 +152,65 @@ func TLSSecretData(t *testing.T, ca Cert, c Cert) map[string][]byte {
"tls.key": c.KeyPEM.Bytes(),
}
}

// -----------------------------------------------------------------------------
// TLS Certificate test helper functions and types
// -----------------------------------------------------------------------------

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

// generateRSACert generates a basic self signed certificate valid for a year
func generateRSACert(hosts []string, keyOut, certOut io.Writer) error {
priv, 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, &template, &priv.PublicKey, priv)
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(priv)}); err != nil {
return fmt.Errorf("failed creating key: %w", err)
}

return nil
}
109 changes: 100 additions & 9 deletions pkg/utils/test/generators.go → test/helpers/generators.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package test
package helpers

import (
"bytes"
"testing"

"github.com/google/uuid"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -12,17 +17,26 @@ import (
gwtypes "github.com/kong/gateway-operator/internal/types"
"github.com/kong/gateway-operator/pkg/consts"
"github.com/kong/gateway-operator/pkg/vars"
"github.com/kong/gateway-operator/test/helpers"
)

// GenerateGatewayClass generates the default GatewayClass to be used in tests
func GenerateGatewayClass() *gatewayv1.GatewayClass {
// MustGenerateGatewayClass generates the default GatewayClass to be used in tests
func MustGenerateGatewayClass(t *testing.T, parametersRefs ...gatewayv1.ParametersReference) *gatewayv1.GatewayClass {
t.Helper()

if len(parametersRefs) > 1 {
require.Fail(t, "only one ParametersReference is allowed")
}
var parametersRef *gatewayv1.ParametersReference
if len(parametersRefs) == 1 {
parametersRef = &parametersRefs[0]
}
gatewayClass := &gatewayv1.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: uuid.NewString(),
},
Spec: gatewayv1.GatewayClassSpec{
ControllerName: gatewayv1.GatewayController(vars.ControllerName()),
ParametersRef: parametersRef,
},
}
return gatewayClass
Expand Down Expand Up @@ -53,11 +67,11 @@ func GenerateGateway(gatewayNSN types.NamespacedName, gatewayClass *gatewayv1.Ga
}

// GenerateGatewayConfiguration generates a GatewayConfiguration to be used in tests
func GenerateGatewayConfiguration(gatewayConfigurationNSN types.NamespacedName) *operatorv1beta1.GatewayConfiguration {
func GenerateGatewayConfiguration(namespace string) *operatorv1beta1.GatewayConfiguration {
return &operatorv1beta1.GatewayConfiguration{
ObjectMeta: metav1.ObjectMeta{
Namespace: gatewayConfigurationNSN.Namespace,
Name: gatewayConfigurationNSN.Name,
Namespace: namespace,
Name: uuid.NewString(),
},
Spec: operatorv1beta1.GatewayConfigurationSpec{
ControlPlaneOptions: &operatorv1beta1.ControlPlaneOptions{
Expand All @@ -82,6 +96,12 @@ func GenerateGatewayConfiguration(gatewayConfigurationNSN types.NamespacedName)
},
},
},
Env: []corev1.EnvVar{
{
Name: "CONTROLLER_LOG_LEVEL",
Value: "trace",
},
},
},
},
},
Expand All @@ -96,7 +116,7 @@ func GenerateGatewayConfiguration(gatewayConfigurationNSN types.NamespacedName)
Containers: []corev1.Container{
{
Name: consts.DataPlaneProxyContainerName,
Image: helpers.GetDefaultDataPlaneImage(),
Image: GetDefaultDataPlaneImage(),
ReadinessProbe: &corev1.Probe{
FailureThreshold: 3,
InitialDelaySeconds: 0,
Expand All @@ -105,7 +125,7 @@ func GenerateGatewayConfiguration(gatewayConfigurationNSN types.NamespacedName)
TimeoutSeconds: 1,
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/status",
Path: "/status/ready",
Port: intstr.FromInt(consts.DataPlaneMetricsPort),
Scheme: corev1.URISchemeHTTP,
},
Expand All @@ -121,3 +141,74 @@ func GenerateGatewayConfiguration(gatewayConfigurationNSN types.NamespacedName)
},
}
}

// GenerateHTTPRoute generates an HTTPRoute to be used in tests
func GenerateHTTPRoute(namespace string, gatewayName, serviceName string, opts ...func(*gatewayv1.HTTPRoute)) *gatewayv1.HTTPRoute {
httpRoute := &gatewayv1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: uuid.NewString(),
Annotations: map[string]string{
"konghq.com/strip-path": "true",
},
},
Spec: gatewayv1.HTTPRouteSpec{
CommonRouteSpec: gatewayv1.CommonRouteSpec{
ParentRefs: []gatewayv1.ParentReference{{
Name: gatewayv1.ObjectName(gatewayName),
}},
},
Rules: []gatewayv1.HTTPRouteRule{
{
Matches: []gatewayv1.HTTPRouteMatch{
{
Path: &gatewayv1.HTTPPathMatch{
Type: lo.ToPtr(gatewayv1.PathMatchPathPrefix),
Value: lo.ToPtr("/test"),
},
},
},
BackendRefs: []gatewayv1.HTTPBackendRef{
{
BackendRef: gatewayv1.BackendRef{
BackendObjectReference: gatewayv1.BackendObjectReference{
Name: gatewayv1.ObjectName(serviceName),
Port: lo.ToPtr(gatewayv1.PortNumber(80)),
Kind: lo.ToPtr(gatewayv1.Kind("Service")),
},
},
},
},
},
},
},
}

for _, opt := range opts {
opt(httpRoute)
}

return httpRoute
}

// MustGenerateTLSSecret generates a TLS secret to be used in tests
func MustGenerateTLSSecret(t *testing.T, namespace, secretName string, hosts []string) *corev1.Secret {
t.Helper()

var serverKey, serverCert bytes.Buffer
require.NoError(t, generateRSACert(hosts, &serverKey, &serverCert), "failed to generate RSA certificate")

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

return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: secretName,
},
Type: corev1.SecretTypeTLS,
Data: data,
}
}
Loading

0 comments on commit 1929b68

Please sign in to comment.