Skip to content

Commit

Permalink
add tls handshake integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jcmoraisjr committed Jun 5, 2024
1 parent 7bbf160 commit f829893
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 89 deletions.
61 changes: 46 additions & 15 deletions tests/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package framework
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"math/rand"
Expand Down Expand Up @@ -234,6 +235,12 @@ func (f *framework) Request(ctx context.Context, t *testing.T, method, host, pat
ServerName: opt.SNI,
},
}
if opt.ClientCA != nil {
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM(opt.ClientCA)
require.True(t, ok)
transport.TLSClientConfig.RootCAs = pool
}
if opt.ClientCrtPEM != nil && opt.ClientKeyPEM != nil {
cert, err := tls.X509KeyPair(opt.ClientCrtPEM, opt.ClientKeyPEM)
require.NoError(t, err)
Expand All @@ -254,15 +261,25 @@ func (f *framework) Request(ctx context.Context, t *testing.T, method, host, pat
Transport: transport,
}
var res *http.Response
if opt.ExpectResponseCode > 0 {
switch {
case opt.ExpectResponseCode > 0:
require.EventuallyWithT(t, func(collect *assert.CollectT) {
res, err = cli.Do(req)
if !assert.NoError(collect, err) {
return
}
assert.Equal(collect, opt.ExpectResponseCode, res.StatusCode)
}, 5*time.Second, time.Second)
} else {
case opt.ExpectX509Error != "":
require.EventuallyWithT(t, func(collect *assert.CollectT) {
_, err := cli.Do(req)
// better if matching some x509.<...>Error{} instead,
// but error.Is() does not render to true due to the server's
// x509 certificate attached to the error instance.
assert.ErrorContains(collect, err, opt.ExpectX509Error)
}, 5*time.Second, time.Second)
return Response{EchoResponse: buildEchoResponse(t, "")}
default:
res, err = cli.Do(req)
require.NoError(t, err)
}
Expand Down Expand Up @@ -307,7 +324,7 @@ metadata:
name: ""
namespace: default
`
name := randomName("secret")
name := RandomName("secret")

secret := f.CreateObject(t, data).(*corev1.Secret)
secret.Name = name
Expand All @@ -329,6 +346,13 @@ metadata:
return secret
}

func (f *framework) CreateSecretTLS(ctx context.Context, t *testing.T, crt, key []byte, o ...options.Object) *corev1.Secret {
return f.CreateSecret(ctx, t, map[string][]byte{
corev1.TLSCertKey: crt,
corev1.TLSPrivateKeyKey: key,
})
}

func (f *framework) CreateService(ctx context.Context, t *testing.T, serverPort int32, o ...options.Object) *corev1.Service {
opt := options.ParseObjectOptions(o...)
data := `
Expand Down Expand Up @@ -380,7 +404,7 @@ subsets:
ports:
- port: 0
`
name := randomName("svc")
name := RandomName("svc")

ep := f.CreateObject(t, data).(*corev1.Endpoints)
ep.Name = name
Expand Down Expand Up @@ -424,15 +448,26 @@ spec:
port:
number: 8080
`
name := randomName("ing")
hostname := name + ".local"
var name, hostname string
if h := opt.IngressOpt.CustomHostName; h == "" {
name = RandomName("ing")
hostname = name + ".local"
} else {
name = strings.Split(h, ".")[0]
hostname = h
}

ing := f.CreateObject(t, data).(*networking.Ingress)
ing.Name = name
ing.Spec.Rules[0].Host = hostname
ing.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = svc.Name
opt.Apply(ing)
if opt.IngressOpt.DefaultTLS {
if opt.IngressOpt.CustomTLSSecret != "" {
ing.Spec.TLS = []networking.IngressTLS{{
Hosts: []string{hostname},
SecretName: opt.IngressOpt.CustomTLSSecret,
}}
} else if opt.IngressOpt.DefaultTLS {
ing.Spec.TLS = []networking.IngressTLS{{Hosts: []string{hostname}}}
}

Expand Down Expand Up @@ -474,7 +509,7 @@ metadata:
spec:
controllerName: haproxy-ingress.github.io/controller
`, api)
name := randomName("gc")
name := RandomName("gc")

gc := f.CreateObject(t, data)
gc.SetName(name)
Expand Down Expand Up @@ -528,7 +563,7 @@ metadata:
spec:
gatewayClassName: ""
`, api)
name := randomName("gw")
name := RandomName("gw")

gw := f.CreateObject(t, data)
gw.SetName(name)
Expand Down Expand Up @@ -594,7 +629,7 @@ spec:
- name: ""
port: 0
`, api)
name := randomName("httproute")
name := RandomName("httproute")
hostname := name + ".local"

route := f.CreateObject(t, data)
Expand Down Expand Up @@ -647,7 +682,7 @@ spec:
- name: ""
port: 0
`, api)
name := randomName("tcproute")
name := RandomName("tcproute")

route := f.CreateObject(t, data)
route.SetName(name)
Expand Down Expand Up @@ -774,7 +809,3 @@ func (f *framework) CreateTCPServer(ctx context.Context, t *testing.T) int32 {
}()
return serverPort
}

func randomName(prefix string) string {
return fmt.Sprintf("%s-%08d", prefix, rand.Intn(1e8))
}
27 changes: 27 additions & 0 deletions tests/framework/options/certificates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package options

type Certificate func(o *certificateOpt)

func DNS(dns ...string) Certificate {
return func(o *certificateOpt) {
o.DNS = dns
}
}

func InvalidDates() Certificate {
return func(o *certificateOpt) {
o.InvalidDates = true
}
}

type certificateOpt struct {
DNS []string
InvalidDates bool
}

func ParseCertificateOptions(opts ...Certificate) (opt certificateOpt) {
for _, o := range opts {
o(&opt)
}
return opt
}
17 changes: 15 additions & 2 deletions tests/framework/options/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,23 @@ func AddConfigKeyAnnotation(key, value string) Object {
}
}

func DefaultHostTLS() Object {
func DefaultTLS() Object {
return func(o *objectOpt) {
o.IngressOpt.DefaultTLS = true
}
}

func CustomTLS(secret string) Object {
return func(o *objectOpt) {
o.IngressOpt.CustomTLSSecret = secret
}
}

func CustomHostName(hostname string) Object {
return func(o *objectOpt) {
o.IngressOpt.CustomHostName = hostname
}
}
func Listener(name, proto string, port int32) Object {
return func(o *objectOpt) {
o.GatewayOpt.Listeners = append(o.GatewayOpt.Listeners, ListenerOpt{
Expand All @@ -45,7 +56,9 @@ type objectOpt struct {
}

type IngressOpt struct {
DefaultTLS bool
DefaultTLS bool
CustomTLSSecret string
CustomHostName string
}

type GatewayOpt struct {
Expand Down
22 changes: 18 additions & 4 deletions tests/framework/options/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,27 @@ func ExpectResponseCode(code int) Request {
}
}

func HTTPSRequest(https bool) Request {
func ExpectX509Error(msg string) Request {
return func(o *requestOpt) {
o.HTTPS = https
o.ExpectX509Error = msg
}
}

func TLSSkipVerify(skipVerify bool) Request {
func HTTPSRequest() Request {
return func(o *requestOpt) {
o.TLSSkipVerify = skipVerify
o.HTTPS = true
}
}

func TLSSkipVerify() Request {
return func(o *requestOpt) {
o.TLSSkipVerify = true
}
}

func ClientCA(ca []byte) Request {
return func(o *requestOpt) {
o.ClientCA = ca
}
}

Expand All @@ -35,8 +47,10 @@ func ClientCertificateKeyPEM(crt, key []byte) Request {

type requestOpt struct {
ExpectResponseCode int
ExpectX509Error string
HTTPS bool
TLSSkipVerify bool
ClientCA []byte
SNI string
ClientCrtPEM []byte
ClientKeyPEM []byte
Expand Down
25 changes: 18 additions & 7 deletions tests/framework/ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ import (
"time"

"github.com/stretchr/testify/require"

"github.com/jcmoraisjr/haproxy-ingress/tests/framework/options"
)

const (
CertificateIssuerCN = "HAProxy Ingress issuer"
CertificateClientCN = "HAProxy Ingress client"
)

func CreateCA(t *testing.T, dns ...string) (ca, key []byte) {
func CreateCA(t *testing.T, cn string, dns ...string) (ca, key []byte) {
serial, err := rand.Int(rand.Reader, big.NewInt(2^63))
require.NoError(t, err)
notBefore := time.Now().Add(-time.Hour)
notAfter := notBefore.Add(24 * time.Hour)
template := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: CertificateIssuerCN,
CommonName: cn,
},
NotBefore: notBefore,
NotAfter: notAfter,
Expand All @@ -45,7 +47,9 @@ func CreateCA(t *testing.T, dns ...string) (ca, key []byte) {
return ca, key
}

func CreateCertificate(t *testing.T, ca, cakey []byte, dns ...string) (crt, key []byte) {
func CreateCertificate(t *testing.T, ca, cakey []byte, cn string, o ...options.Certificate) (crt, key []byte) {
opt := options.ParseCertificateOptions(o...)

cakeyder, _ := pem.Decode(cakey)
cakeyrsa, err := x509.ParsePKCS1PrivateKey(cakeyder.Bytes)
require.NoError(t, err)
Expand All @@ -56,16 +60,23 @@ func CreateCertificate(t *testing.T, ca, cakey []byte, dns ...string) (crt, key

serial, err := rand.Int(rand.Reader, big.NewInt(2^63))
require.NoError(t, err)
notBefore := time.Now().Add(-time.Hour)
notAfter := notBefore.Add(24 * time.Hour)

var notBefore, notAfter time.Time
if opt.InvalidDates {
notBefore = time.Now().Add(-24 * time.Hour)
notAfter = notBefore.Add(12 * time.Hour)
} else {
notBefore = time.Now().Add(-time.Hour)
notAfter = notBefore.Add(24 * time.Hour)
}
template := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: CertificateClientCN,
CommonName: cn,
},
NotBefore: notBefore,
NotAfter: notAfter,
DNSNames: dns,
DNSNames: opt.DNS,
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
Expand Down
21 changes: 21 additions & 0 deletions tests/framework/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package framework

import (
"fmt"
"math/rand"
)

func RandomName(prefix string) string {
return fmt.Sprintf("%s-%08d", prefix, rand.Intn(1e8))
}

func AppendStringMap(src, add map[string]string) map[string]string {
out := make(map[string]string)
for k, v := range src {
out[k] = v
}
for k, v := range add {
out[k] = v
}
return out
}
Loading

0 comments on commit f829893

Please sign in to comment.