add e2e tests

This commit is contained in:
Chotiwat Chawannakul 2024-02-16 01:38:01 -08:00
parent 6473481c85
commit 8292585bb4
2 changed files with 215 additions and 102 deletions

View file

@ -80,22 +80,41 @@ func (h *HTTPRequest) ForceResolve(ip string, port uint16) *HTTPRequest {
h.chain.fail(fmt.Sprintf("invalid ip address: %s", ip)) h.chain.fail(fmt.Sprintf("invalid ip address: %s", ip))
return h return h
} }
dialer := &net.Dialer{
Timeout: h.client.Timeout,
KeepAlive: h.client.Timeout,
DualStack: true,
}
resolveAddr := fmt.Sprintf("%s:%d", ip, int(port)) resolveAddr := fmt.Sprintf("%s:%d", ip, int(port))
return h.WithDialContextMiddleware(func(next DialContextFunc) DialContextFunc {
return func(ctx context.Context, network, _ string) (net.Conn, error) {
return next(ctx, network, resolveAddr)
}
})
}
// DialContextFunc is the function signature for `DialContext`
type DialContextFunc func(ctx context.Context, network, addr string) (net.Conn, error)
// WithDialContextMiddleware sets the `DialContext` function of the client
// transport with a new function returns from `fn`. An existing `DialContext`
// is passed into `fn` so the new function can act as a middleware by calling
// the old one.
func (h *HTTPRequest) WithDialContextMiddleware(fn func(next DialContextFunc) DialContextFunc) *HTTPRequest {
oldTransport, ok := h.client.Transport.(*http.Transport) oldTransport, ok := h.client.Transport.(*http.Transport)
if !ok { if !ok {
h.chain.fail("invalid old transport address") h.chain.fail("invalid old transport address")
return h return h
} }
newTransport := oldTransport.Clone() var nextDialContext DialContextFunc
newTransport.DialContext = func(ctx context.Context, network, _ string) (net.Conn, error) { if oldTransport.DialContext != nil {
return dialer.DialContext(ctx, network, resolveAddr) nextDialContext = oldTransport.DialContext
} else {
dialer := &net.Dialer{
Timeout: h.client.Timeout,
KeepAlive: h.client.Timeout,
DualStack: true,
} }
nextDialContext = dialer.DialContext
}
newTransport := oldTransport.Clone()
newTransport.DialContext = fn(nextDialContext)
h.client.Transport = newTransport h.client.Transport = newTransport
return h return h
} }

View file

@ -27,10 +27,10 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/test/e2e/framework" "k8s.io/ingress-nginx/test/e2e/framework"
"k8s.io/ingress-nginx/test/e2e/framework/httpexpect"
) )
var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() { var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
@ -75,26 +75,29 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
Status(http.StatusNotFound) Status(http.StatusNotFound)
}) })
ginkgo.It("should pass unknown traffic to default backend and handle known traffic", func() { ginkgo.Context("when handling traffic", func() {
var tlsConfig *tls.Config
host := "testpassthrough.com" host := "testpassthrough.com"
echoName := "echopass" echoName := "echopass"
secretName := host
ginkgo.BeforeEach(func() {
/* Even with enable-ssl-passthrough enabled, only annotated ingresses may receive the traffic */ /* Even with enable-ssl-passthrough enabled, only annotated ingresses may receive the traffic */
annotations := map[string]string{ annotations := map[string]string{
"nginx.ingress.kubernetes.io/ssl-passthrough": "true", "nginx.ingress.kubernetes.io/ssl-passthrough": "true",
} }
ingressDef := framework.NewSingleIngressWithTLS(host, ingressDef := framework.NewSingleIngress(host,
"/", "/",
host, host,
[]string{host},
f.Namespace, f.Namespace,
echoName, echoName,
80, 80,
annotations) annotations)
tlsConfig, err := framework.CreateIngressTLSSecret(f.KubeClientSet, var err error
ingressDef.Spec.TLS[0].Hosts, tlsConfig, err = framework.CreateIngressTLSSecret(f.KubeClientSet,
ingressDef.Spec.TLS[0].SecretName, []string{host},
secretName,
ingressDef.Namespace) ingressDef.Namespace)
volumeMount := []corev1.VolumeMount{ volumeMount := []corev1.VolumeMount{
@ -109,7 +112,7 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
Name: "certs", Name: "certs",
VolumeSource: corev1.VolumeSource{ VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{ Secret: &corev1.SecretVolumeSource{
SecretName: ingressDef.Spec.TLS[0].SecretName, SecretName: secretName,
}, },
}, },
}, },
@ -145,7 +148,9 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
func(server string) bool { func(server string) bool {
return strings.Contains(server, "listen 442") return strings.Contains(server, "listen 442")
}) })
})
ginkgo.It("should pass unknown traffic to default backend and handle known traffic", func() {
/* This one should not receive traffic as it does not contain passthrough annotation */ /* This one should not receive traffic as it does not contain passthrough annotation */
hostBad := "noannotationnopassthrough.com" hostBad := "noannotationnopassthrough.com"
ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad, ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad,
@ -183,6 +188,95 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
ForceResolve(f.GetNginxIP(), 443). ForceResolve(f.GetNginxIP(), 443).
Expect(). Expect().
Status(http.StatusNotFound) Status(http.StatusNotFound)
//nolint:gosec // Ignore the gosec error in testing
f.HTTPTestClientWithTLSConfig(tlsConfig).
GET("/").
WithURL("https://"+net.JoinHostPort(host, "443")).
ForceResolve(f.GetNginxIP(), 443).
Expect().
Status(http.StatusOK)
//nolint:gosec // Ignore the gosec error in testing
f.HTTPTestClientWithTLSConfig(tlsConfigBad).
GET("/").
WithURL("https://"+net.JoinHostPort(hostBad, "443")).
ForceResolve(f.GetNginxIP(), 443).
Expect().
Status(http.StatusNotFound)
})
ginkgo.Context("on throttled connections", func() {
throttleMiddleware := func(next httpexpect.DialContextFunc) httpexpect.DialContextFunc {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
// Wrap the connection with a throttled writer to simulate real
// world traffic where streaming data may arrive in chunks
conn, err := next(ctx, network, addr)
return &writeThrottledConn{
Conn: conn,
chunkSize: 50,
}, err
}
}
tries := 3
ginkgo.It("should handle known traffic without Host header", func() {
for i := 0; i < tries; i++ {
//nolint:gosec // Ignore the gosec error in testing
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
GET("/").
WithURL("https://"+net.JoinHostPort(host, "443")).
ForceResolve(f.GetNginxIP(), 443).
WithDialContextMiddleware(throttleMiddleware).
Expect().
Status(http.StatusOK)
}
})
ginkgo.It("should handle known traffic with Host header", func() {
for i := 0; i < tries; i++ {
//nolint:gosec // Ignore the gosec error in testing
f.HTTPTestClientWithTLSConfig(tlsConfig).
GET("/").
WithURL("https://"+net.JoinHostPort(host, "443")).
WithHeader("Host", host).
ForceResolve(f.GetNginxIP(), 443).
WithDialContextMiddleware(throttleMiddleware).
Expect().
Status(http.StatusOK)
}
})
ginkgo.It("should handle insecure traffic with Host header", func() {
for i := 0; i < tries; i++ {
//nolint:gosec // Ignore the gosec error in testing
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
GET("/").
WithURL("https://"+net.JoinHostPort(host, "443")).
WithHeader("Host", host).
ForceResolve(f.GetNginxIP(), 443).
WithDialContextMiddleware(throttleMiddleware).
Expect().
Status(http.StatusOK)
}
})
})
}) })
}) })
}) })
type writeThrottledConn struct {
net.Conn
chunkSize int
}
// Write writes data to the connection `chunkSize` bytes (or less) at a time.
func (c *writeThrottledConn) Write(b []byte) (n int, err error) {
for i := 0; i < len(b); i += c.chunkSize {
n, err := c.Conn.Write(b[i:min(i+c.chunkSize, len(b))])
if err != nil {
return i + n, err
}
}
return len(b), nil
}