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

chore(refactor): get rid of duplicated helper for TLS certs, and improve existing ones #212

Merged
merged 1 commit into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 0 additions & 4 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,6 @@ issues:
- revive
text: "exported: exported"
# Test cases are self-explanatory, thus no need a docstring.
- path: test/helpers/certificate
linters:
- revive
text: "exported: exported"
- path: test/integration
linters:
- revive
Expand Down
42 changes: 21 additions & 21 deletions test/helpers/certificate/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,26 @@ type selfSignedCertificateOptions struct {
Expired bool
}

type SelfSignedCertificateOption func(selfSignedCertificateOptions) selfSignedCertificateOptions
type selfSignedCertificateOption func(selfSignedCertificateOptions) selfSignedCertificateOptions

func WithCommonName(commonName string) SelfSignedCertificateOption {
// WithCommonName sets the CommonName field of the certificate.
func WithCommonName(commonName string) selfSignedCertificateOption {
return func(opts selfSignedCertificateOptions) selfSignedCertificateOptions {
opts.CommonName = commonName
return opts
}
}

func WithDNSNames(dnsNames ...string) SelfSignedCertificateOption {
// WithDNSNames sets DNS names for the certificate.
func WithDNSNames(dnsNames ...string) selfSignedCertificateOption {
return func(opts selfSignedCertificateOptions) selfSignedCertificateOptions {
opts.DNSNames = append(opts.DNSNames, dnsNames...)
return opts
}
}

func WithIPAdresses(ipAddresses ...string) SelfSignedCertificateOption {
// WithIPAdresses sets IP addresses for the certificate.
func WithIPAdresses(ipAddresses ...string) selfSignedCertificateOption {
return func(opts selfSignedCertificateOptions) selfSignedCertificateOptions {
for _, ip := range ipAddresses {
opts.IPAddresses = append(opts.IPAddresses, net.ParseIP(ip))
Expand All @@ -51,14 +54,15 @@ func WithIPAdresses(ipAddresses ...string) SelfSignedCertificateOption {
}

// WithCATrue allows to use returned certificate to sign other certificates (uses BasicConstraints extension).
func WithCATrue() SelfSignedCertificateOption {
func WithCATrue() selfSignedCertificateOption {
return func(opts selfSignedCertificateOptions) selfSignedCertificateOptions {
opts.CATrue = true
return opts
}
}

func WithAlreadyExpired() SelfSignedCertificateOption {
// WithAlreadyExpired sets the certificate to be already expired.
func WithAlreadyExpired() selfSignedCertificateOption {
return func(opts selfSignedCertificateOptions) selfSignedCertificateOptions {
opts.Expired = true
return opts
Expand All @@ -67,25 +71,21 @@ func WithAlreadyExpired() SelfSignedCertificateOption {

// MustGenerateSelfSignedCert generates a tls.Certificate struct to be used in TLS client/listener configurations.
// Certificate is self-signed thus returned cert can be used as CA for it.
func MustGenerateSelfSignedCert(decorators ...SelfSignedCertificateOption) tls.Certificate {
func MustGenerateSelfSignedCert(options ...selfSignedCertificateOption) tls.Certificate {
// Generate a new RSA private key.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(fmt.Sprintf("Failed to generate RSA key: %s", err))
}

options := selfSignedCertificateOptions{
CommonName: "",
DNSNames: []string{},
}

for _, decorator := range decorators {
options = decorator(options)
var certOptions selfSignedCertificateOptions
for _, option := range options {
certOptions = option(certOptions)
}

notBefore := time.Now()
notAfter := notBefore.AddDate(1, 0, 0)
if options.Expired {
if certOptions.Expired {
notBefore = notBefore.AddDate(-2, 0, 0)
notAfter = notAfter.AddDate(-2, 0, 0)
}
Expand All @@ -100,14 +100,14 @@ func MustGenerateSelfSignedCert(decorators ...SelfSignedCertificateOption) tls.C
Locality: []string{"San Francisco"},
StreetAddress: []string{"150 Spear Street, Suite 1600"},
PostalCode: []string{"94105"},
CommonName: options.CommonName,
CommonName: certOptions.CommonName,
},
NotBefore: notBefore,
NotAfter: notAfter,
DNSNames: options.DNSNames,
IPAddresses: options.IPAddresses,
DNSNames: certOptions.DNSNames,
IPAddresses: certOptions.IPAddresses,
BasicConstraintsValid: true,
IsCA: options.CATrue,
IsCA: certOptions.CATrue,
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
if err != nil {
Expand All @@ -126,8 +126,8 @@ func MustGenerateSelfSignedCert(decorators ...SelfSignedCertificateOption) tls.C
// MustGenerateSelfSignedCertPEMFormat generates self-signed certificate
// and returns certificate and key in PEM format. Certificate is self-signed
// thus returned cert can be used as CA for it.
func MustGenerateSelfSignedCertPEMFormat(decorators ...SelfSignedCertificateOption) (cert []byte, key []byte) {
tlsCert := MustGenerateSelfSignedCert(decorators...)
func MustGenerateSelfSignedCertPEMFormat(options ...selfSignedCertificateOption) (cert []byte, key []byte) {
tlsCert := MustGenerateSelfSignedCert(options...)

certBlock := &pem.Block{
Type: "CERTIFICATE",
Expand Down
66 changes: 0 additions & 66 deletions test/helpers/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"testing"
"time"

Expand Down Expand Up @@ -152,65 +148,3 @@ 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
}
23 changes: 0 additions & 23 deletions test/helpers/generators.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package helpers

import (
"bytes"
"testing"

"github.com/google/uuid"
Expand Down Expand Up @@ -190,25 +189,3 @@ func GenerateHTTPRoute(namespace string, gatewayName, serviceName string, opts .

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,
}
}
16 changes: 14 additions & 2 deletions test/integration/test_httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
operatorv1beta1 "github.com/kong/gateway-operator/api/v1beta1"
testutils "github.com/kong/gateway-operator/pkg/utils/test"
"github.com/kong/gateway-operator/test/helpers"
"github.com/kong/gateway-operator/test/helpers/certificate"
)

func TestHTTPRoute(t *testing.T) {
Expand Down Expand Up @@ -145,9 +146,20 @@ func TestHTTPRouteWithTLS(t *testing.T) {
Namespace: namespace.Name,
}

host := "integration.tests.org"
const host = "integration.tests.org"
cert, key := certificate.MustGenerateSelfSignedCertPEMFormat(certificate.WithDNSNames(host))

secret := helpers.MustGenerateTLSSecret(t, namespace.Name, host, []string{host})
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace.Name,
Name: host,
},
Type: corev1.SecretTypeTLS,
Data: map[string][]byte{
corev1.TLSCertKey: cert,
corev1.TLSPrivateKeyKey: key,
},
}
t.Logf("deploying Secret %s/%s", secret.Namespace, secret.Name)
secret, err = GetClients().K8sClient.CoreV1().Secrets(namespace.Name).Create(GetCtx(), secret, metav1.CreateOptions{})
require.NoError(t, err)
Expand Down
Loading