add e2e tests
This commit is contained in:
parent
6473481c85
commit
8292585bb4
2 changed files with 215 additions and 102 deletions
|
@ -80,22 +80,41 @@ func (h *HTTPRequest) ForceResolve(ip string, port uint16) *HTTPRequest {
|
|||
h.chain.fail(fmt.Sprintf("invalid ip address: %s", ip))
|
||||
return h
|
||||
}
|
||||
dialer := &net.Dialer{
|
||||
Timeout: h.client.Timeout,
|
||||
KeepAlive: h.client.Timeout,
|
||||
DualStack: true,
|
||||
}
|
||||
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)
|
||||
if !ok {
|
||||
h.chain.fail("invalid old transport address")
|
||||
return h
|
||||
}
|
||||
newTransport := oldTransport.Clone()
|
||||
newTransport.DialContext = func(ctx context.Context, network, _ string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, resolveAddr)
|
||||
var nextDialContext DialContextFunc
|
||||
if oldTransport.DialContext != nil {
|
||||
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
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -27,10 +27,10 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||
"k8s.io/ingress-nginx/test/e2e/framework/httpexpect"
|
||||
)
|
||||
|
||||
var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
|
||||
|
@ -75,114 +75,208 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
|
|||
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"
|
||||
echoName := "echopass"
|
||||
secretName := host
|
||||
|
||||
/* Even with enable-ssl-passthrough enabled, only annotated ingresses may receive the traffic */
|
||||
annotations := map[string]string{
|
||||
"nginx.ingress.kubernetes.io/ssl-passthrough": "true",
|
||||
}
|
||||
ginkgo.BeforeEach(func() {
|
||||
/* Even with enable-ssl-passthrough enabled, only annotated ingresses may receive the traffic */
|
||||
annotations := map[string]string{
|
||||
"nginx.ingress.kubernetes.io/ssl-passthrough": "true",
|
||||
}
|
||||
|
||||
ingressDef := framework.NewSingleIngressWithTLS(host,
|
||||
"/",
|
||||
host,
|
||||
[]string{host},
|
||||
f.Namespace,
|
||||
echoName,
|
||||
80,
|
||||
annotations)
|
||||
tlsConfig, err := framework.CreateIngressTLSSecret(f.KubeClientSet,
|
||||
ingressDef.Spec.TLS[0].Hosts,
|
||||
ingressDef.Spec.TLS[0].SecretName,
|
||||
ingressDef.Namespace)
|
||||
ingressDef := framework.NewSingleIngress(host,
|
||||
"/",
|
||||
host,
|
||||
f.Namespace,
|
||||
echoName,
|
||||
80,
|
||||
annotations)
|
||||
var err error
|
||||
tlsConfig, err = framework.CreateIngressTLSSecret(f.KubeClientSet,
|
||||
[]string{host},
|
||||
secretName,
|
||||
ingressDef.Namespace)
|
||||
|
||||
volumeMount := []corev1.VolumeMount{
|
||||
{
|
||||
Name: "certs",
|
||||
ReadOnly: true,
|
||||
MountPath: "/certs",
|
||||
},
|
||||
}
|
||||
volume := []corev1.Volume{
|
||||
{
|
||||
Name: "certs",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: ingressDef.Spec.TLS[0].SecretName,
|
||||
volumeMount := []corev1.VolumeMount{
|
||||
{
|
||||
Name: "certs",
|
||||
ReadOnly: true,
|
||||
MountPath: "/certs",
|
||||
},
|
||||
}
|
||||
volume := []corev1.Volume{
|
||||
{
|
||||
Name: "certs",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: secretName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
envs := []corev1.EnvVar{
|
||||
{
|
||||
Name: "HTTPBUN_SSL_CERT",
|
||||
Value: "/certs/tls.crt",
|
||||
},
|
||||
{
|
||||
Name: "HTTPBUN_SSL_KEY",
|
||||
Value: "/certs/tls.key",
|
||||
},
|
||||
}
|
||||
}
|
||||
envs := []corev1.EnvVar{
|
||||
{
|
||||
Name: "HTTPBUN_SSL_CERT",
|
||||
Value: "/certs/tls.crt",
|
||||
},
|
||||
{
|
||||
Name: "HTTPBUN_SSL_KEY",
|
||||
Value: "/certs/tls.key",
|
||||
},
|
||||
}
|
||||
|
||||
f.NewDeploymentWithOpts("echopass",
|
||||
framework.HTTPBunImage,
|
||||
80,
|
||||
1,
|
||||
nil,
|
||||
nil,
|
||||
envs,
|
||||
volumeMount,
|
||||
volume,
|
||||
false)
|
||||
f.NewDeploymentWithOpts("echopass",
|
||||
framework.HTTPBunImage,
|
||||
80,
|
||||
1,
|
||||
nil,
|
||||
nil,
|
||||
envs,
|
||||
volumeMount,
|
||||
volume,
|
||||
false)
|
||||
|
||||
f.EnsureIngress(ingressDef)
|
||||
f.EnsureIngress(ingressDef)
|
||||
|
||||
assert.Nil(ginkgo.GinkgoT(), err)
|
||||
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig)
|
||||
assert.Nil(ginkgo.GinkgoT(), err)
|
||||
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return strings.Contains(server, "listen 442")
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
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 */
|
||||
hostBad := "noannotationnopassthrough.com"
|
||||
ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad,
|
||||
"/",
|
||||
hostBad,
|
||||
[]string{hostBad},
|
||||
f.Namespace,
|
||||
echoName,
|
||||
80,
|
||||
nil))
|
||||
tlsConfigBad, err := framework.CreateIngressTLSSecret(f.KubeClientSet,
|
||||
ingBad.Spec.TLS[0].Hosts,
|
||||
ingBad.Spec.TLS[0].SecretName,
|
||||
ingBad.Namespace)
|
||||
assert.Nil(ginkgo.GinkgoT(), err)
|
||||
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfigBad)
|
||||
|
||||
f.WaitForNginxServer(hostBad,
|
||||
func(server string) bool {
|
||||
return strings.Contains(server, "listen 442")
|
||||
})
|
||||
|
||||
//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).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
//nolint:gosec // Ignore the gosec error in testing
|
||||
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: hostBad, InsecureSkipVerify: true}).
|
||||
GET("/").
|
||||
WithURL("https://"+net.JoinHostPort(hostBad, "443")).
|
||||
ForceResolve(f.GetNginxIP(), 443).
|
||||
Expect().
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
/* This one should not receive traffic as it does not contain passthrough annotation */
|
||||
hostBad := "noannotationnopassthrough.com"
|
||||
ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad,
|
||||
"/",
|
||||
hostBad,
|
||||
[]string{hostBad},
|
||||
f.Namespace,
|
||||
echoName,
|
||||
80,
|
||||
nil))
|
||||
tlsConfigBad, err := framework.CreateIngressTLSSecret(f.KubeClientSet,
|
||||
ingBad.Spec.TLS[0].Hosts,
|
||||
ingBad.Spec.TLS[0].SecretName,
|
||||
ingBad.Namespace)
|
||||
assert.Nil(ginkgo.GinkgoT(), err)
|
||||
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfigBad)
|
||||
|
||||
f.WaitForNginxServer(hostBad,
|
||||
func(server string) bool {
|
||||
return strings.Contains(server, "listen 442")
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
//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).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
//nolint:gosec // Ignore the gosec error in testing
|
||||
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: hostBad, InsecureSkipVerify: true}).
|
||||
GET("/").
|
||||
WithURL("https://"+net.JoinHostPort(hostBad, "443")).
|
||||
ForceResolve(f.GetNginxIP(), 443).
|
||||
Expect().
|
||||
Status(http.StatusNotFound)
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue