Skip to content

Commit 8292585

Browse files
committed
add e2e tests
1 parent 6473481 commit 8292585

File tree

2 files changed

+221
-108
lines changed

2 files changed

+221
-108
lines changed

test/e2e/framework/httpexpect/request.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,41 @@ func (h *HTTPRequest) ForceResolve(ip string, port uint16) *HTTPRequest {
8080
h.chain.fail(fmt.Sprintf("invalid ip address: %s", ip))
8181
return h
8282
}
83-
dialer := &net.Dialer{
84-
Timeout: h.client.Timeout,
85-
KeepAlive: h.client.Timeout,
86-
DualStack: true,
87-
}
8883
resolveAddr := fmt.Sprintf("%s:%d", ip, int(port))
8984

85+
return h.WithDialContextMiddleware(func(next DialContextFunc) DialContextFunc {
86+
return func(ctx context.Context, network, _ string) (net.Conn, error) {
87+
return next(ctx, network, resolveAddr)
88+
}
89+
})
90+
}
91+
92+
// DialContextFunc is the function signature for `DialContext`
93+
type DialContextFunc func(ctx context.Context, network, addr string) (net.Conn, error)
94+
95+
// WithDialContextMiddleware sets the `DialContext` function of the client
96+
// transport with a new function returns from `fn`. An existing `DialContext`
97+
// is passed into `fn` so the new function can act as a middleware by calling
98+
// the old one.
99+
func (h *HTTPRequest) WithDialContextMiddleware(fn func(next DialContextFunc) DialContextFunc) *HTTPRequest {
90100
oldTransport, ok := h.client.Transport.(*http.Transport)
91101
if !ok {
92102
h.chain.fail("invalid old transport address")
93103
return h
94104
}
95-
newTransport := oldTransport.Clone()
96-
newTransport.DialContext = func(ctx context.Context, network, _ string) (net.Conn, error) {
97-
return dialer.DialContext(ctx, network, resolveAddr)
105+
var nextDialContext DialContextFunc
106+
if oldTransport.DialContext != nil {
107+
nextDialContext = oldTransport.DialContext
108+
} else {
109+
dialer := &net.Dialer{
110+
Timeout: h.client.Timeout,
111+
KeepAlive: h.client.Timeout,
112+
DualStack: true,
113+
}
114+
nextDialContext = dialer.DialContext
98115
}
116+
newTransport := oldTransport.Clone()
117+
newTransport.DialContext = fn(nextDialContext)
99118
h.client.Transport = newTransport
100119
return h
101120
}

test/e2e/settings/ssl_passthrough.go

Lines changed: 194 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import (
2727
"github.com/stretchr/testify/assert"
2828
appsv1 "k8s.io/api/apps/v1"
2929
corev1 "k8s.io/api/core/v1"
30-
3130
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3231

3332
"k8s.io/ingress-nginx/test/e2e/framework"
33+
"k8s.io/ingress-nginx/test/e2e/framework/httpexpect"
3434
)
3535

3636
var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
@@ -75,114 +75,208 @@ var _ = framework.IngressNginxDescribe("[Flag] enable-ssl-passthrough", func() {
7575
Status(http.StatusNotFound)
7676
})
7777

78-
ginkgo.It("should pass unknown traffic to default backend and handle known traffic", func() {
78+
ginkgo.Context("when handling traffic", func() {
79+
var tlsConfig *tls.Config
7980
host := "testpassthrough.com"
8081
echoName := "echopass"
82+
secretName := host
83+
84+
ginkgo.BeforeEach(func() {
85+
/* Even with enable-ssl-passthrough enabled, only annotated ingresses may receive the traffic */
86+
annotations := map[string]string{
87+
"nginx.ingress.kubernetes.io/ssl-passthrough": "true",
88+
}
8189

82-
/* Even with enable-ssl-passthrough enabled, only annotated ingresses may receive the traffic */
83-
annotations := map[string]string{
84-
"nginx.ingress.kubernetes.io/ssl-passthrough": "true",
85-
}
86-
87-
ingressDef := framework.NewSingleIngressWithTLS(host,
88-
"/",
89-
host,
90-
[]string{host},
91-
f.Namespace,
92-
echoName,
93-
80,
94-
annotations)
95-
tlsConfig, err := framework.CreateIngressTLSSecret(f.KubeClientSet,
96-
ingressDef.Spec.TLS[0].Hosts,
97-
ingressDef.Spec.TLS[0].SecretName,
98-
ingressDef.Namespace)
99-
100-
volumeMount := []corev1.VolumeMount{
101-
{
102-
Name: "certs",
103-
ReadOnly: true,
104-
MountPath: "/certs",
105-
},
106-
}
107-
volume := []corev1.Volume{
108-
{
109-
Name: "certs",
110-
VolumeSource: corev1.VolumeSource{
111-
Secret: &corev1.SecretVolumeSource{
112-
SecretName: ingressDef.Spec.TLS[0].SecretName,
90+
ingressDef := framework.NewSingleIngress(host,
91+
"/",
92+
host,
93+
f.Namespace,
94+
echoName,
95+
80,
96+
annotations)
97+
var err error
98+
tlsConfig, err = framework.CreateIngressTLSSecret(f.KubeClientSet,
99+
[]string{host},
100+
secretName,
101+
ingressDef.Namespace)
102+
103+
volumeMount := []corev1.VolumeMount{
104+
{
105+
Name: "certs",
106+
ReadOnly: true,
107+
MountPath: "/certs",
108+
},
109+
}
110+
volume := []corev1.Volume{
111+
{
112+
Name: "certs",
113+
VolumeSource: corev1.VolumeSource{
114+
Secret: &corev1.SecretVolumeSource{
115+
SecretName: secretName,
116+
},
113117
},
114118
},
115-
},
116-
}
117-
envs := []corev1.EnvVar{
118-
{
119-
Name: "HTTPBUN_SSL_CERT",
120-
Value: "/certs/tls.crt",
121-
},
122-
{
123-
Name: "HTTPBUN_SSL_KEY",
124-
Value: "/certs/tls.key",
125-
},
126-
}
127-
128-
f.NewDeploymentWithOpts("echopass",
129-
framework.HTTPBunImage,
130-
80,
131-
1,
132-
nil,
133-
nil,
134-
envs,
135-
volumeMount,
136-
volume,
137-
false)
138-
139-
f.EnsureIngress(ingressDef)
140-
141-
assert.Nil(ginkgo.GinkgoT(), err)
142-
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig)
143-
144-
f.WaitForNginxServer(host,
145-
func(server string) bool {
146-
return strings.Contains(server, "listen 442")
147-
})
119+
}
120+
envs := []corev1.EnvVar{
121+
{
122+
Name: "HTTPBUN_SSL_CERT",
123+
Value: "/certs/tls.crt",
124+
},
125+
{
126+
Name: "HTTPBUN_SSL_KEY",
127+
Value: "/certs/tls.key",
128+
},
129+
}
148130

149-
/* This one should not receive traffic as it does not contain passthrough annotation */
150-
hostBad := "noannotationnopassthrough.com"
151-
ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad,
152-
"/",
153-
hostBad,
154-
[]string{hostBad},
155-
f.Namespace,
156-
echoName,
157-
80,
158-
nil))
159-
tlsConfigBad, err := framework.CreateIngressTLSSecret(f.KubeClientSet,
160-
ingBad.Spec.TLS[0].Hosts,
161-
ingBad.Spec.TLS[0].SecretName,
162-
ingBad.Namespace)
163-
assert.Nil(ginkgo.GinkgoT(), err)
164-
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfigBad)
165-
166-
f.WaitForNginxServer(hostBad,
167-
func(server string) bool {
168-
return strings.Contains(server, "listen 442")
131+
f.NewDeploymentWithOpts("echopass",
132+
framework.HTTPBunImage,
133+
80,
134+
1,
135+
nil,
136+
nil,
137+
envs,
138+
volumeMount,
139+
volume,
140+
false)
141+
142+
f.EnsureIngress(ingressDef)
143+
144+
assert.Nil(ginkgo.GinkgoT(), err)
145+
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfig)
146+
147+
f.WaitForNginxServer(host,
148+
func(server string) bool {
149+
return strings.Contains(server, "listen 442")
150+
})
151+
})
152+
153+
ginkgo.It("should pass unknown traffic to default backend and handle known traffic", func() {
154+
/* This one should not receive traffic as it does not contain passthrough annotation */
155+
hostBad := "noannotationnopassthrough.com"
156+
ingBad := f.EnsureIngress(framework.NewSingleIngressWithTLS(hostBad,
157+
"/",
158+
hostBad,
159+
[]string{hostBad},
160+
f.Namespace,
161+
echoName,
162+
80,
163+
nil))
164+
tlsConfigBad, err := framework.CreateIngressTLSSecret(f.KubeClientSet,
165+
ingBad.Spec.TLS[0].Hosts,
166+
ingBad.Spec.TLS[0].SecretName,
167+
ingBad.Namespace)
168+
assert.Nil(ginkgo.GinkgoT(), err)
169+
framework.WaitForTLS(f.GetURL(framework.HTTPS), tlsConfigBad)
170+
171+
f.WaitForNginxServer(hostBad,
172+
func(server string) bool {
173+
return strings.Contains(server, "listen 442")
174+
})
175+
176+
//nolint:gosec // Ignore the gosec error in testing
177+
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
178+
GET("/").
179+
WithURL("https://"+net.JoinHostPort(host, "443")).
180+
ForceResolve(f.GetNginxIP(), 443).
181+
Expect().
182+
Status(http.StatusOK)
183+
184+
//nolint:gosec // Ignore the gosec error in testing
185+
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: hostBad, InsecureSkipVerify: true}).
186+
GET("/").
187+
WithURL("https://"+net.JoinHostPort(hostBad, "443")).
188+
ForceResolve(f.GetNginxIP(), 443).
189+
Expect().
190+
Status(http.StatusNotFound)
191+
192+
//nolint:gosec // Ignore the gosec error in testing
193+
f.HTTPTestClientWithTLSConfig(tlsConfig).
194+
GET("/").
195+
WithURL("https://"+net.JoinHostPort(host, "443")).
196+
ForceResolve(f.GetNginxIP(), 443).
197+
Expect().
198+
Status(http.StatusOK)
199+
200+
//nolint:gosec // Ignore the gosec error in testing
201+
f.HTTPTestClientWithTLSConfig(tlsConfigBad).
202+
GET("/").
203+
WithURL("https://"+net.JoinHostPort(hostBad, "443")).
204+
ForceResolve(f.GetNginxIP(), 443).
205+
Expect().
206+
Status(http.StatusNotFound)
207+
})
208+
209+
ginkgo.Context("on throttled connections", func() {
210+
throttleMiddleware := func(next httpexpect.DialContextFunc) httpexpect.DialContextFunc {
211+
return func(ctx context.Context, network, addr string) (net.Conn, error) {
212+
// Wrap the connection with a throttled writer to simulate real
213+
// world traffic where streaming data may arrive in chunks
214+
conn, err := next(ctx, network, addr)
215+
return &writeThrottledConn{
216+
Conn: conn,
217+
chunkSize: 50,
218+
}, err
219+
}
220+
}
221+
tries := 3
222+
223+
ginkgo.It("should handle known traffic without Host header", func() {
224+
for i := 0; i < tries; i++ {
225+
//nolint:gosec // Ignore the gosec error in testing
226+
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
227+
GET("/").
228+
WithURL("https://"+net.JoinHostPort(host, "443")).
229+
ForceResolve(f.GetNginxIP(), 443).
230+
WithDialContextMiddleware(throttleMiddleware).
231+
Expect().
232+
Status(http.StatusOK)
233+
}
169234
})
170235

171-
//nolint:gosec // Ignore the gosec error in testing
172-
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
173-
GET("/").
174-
WithURL("https://"+net.JoinHostPort(host, "443")).
175-
ForceResolve(f.GetNginxIP(), 443).
176-
Expect().
177-
Status(http.StatusOK)
236+
ginkgo.It("should handle known traffic with Host header", func() {
237+
for i := 0; i < tries; i++ {
238+
//nolint:gosec // Ignore the gosec error in testing
239+
f.HTTPTestClientWithTLSConfig(tlsConfig).
240+
GET("/").
241+
WithURL("https://"+net.JoinHostPort(host, "443")).
242+
WithHeader("Host", host).
243+
ForceResolve(f.GetNginxIP(), 443).
244+
WithDialContextMiddleware(throttleMiddleware).
245+
Expect().
246+
Status(http.StatusOK)
247+
}
248+
})
178249

179-
//nolint:gosec // Ignore the gosec error in testing
180-
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: hostBad, InsecureSkipVerify: true}).
181-
GET("/").
182-
WithURL("https://"+net.JoinHostPort(hostBad, "443")).
183-
ForceResolve(f.GetNginxIP(), 443).
184-
Expect().
185-
Status(http.StatusNotFound)
250+
ginkgo.It("should handle insecure traffic with Host header", func() {
251+
for i := 0; i < tries; i++ {
252+
//nolint:gosec // Ignore the gosec error in testing
253+
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
254+
GET("/").
255+
WithURL("https://"+net.JoinHostPort(host, "443")).
256+
WithHeader("Host", host).
257+
ForceResolve(f.GetNginxIP(), 443).
258+
WithDialContextMiddleware(throttleMiddleware).
259+
Expect().
260+
Status(http.StatusOK)
261+
}
262+
})
263+
})
186264
})
187265
})
188266
})
267+
268+
type writeThrottledConn struct {
269+
net.Conn
270+
chunkSize int
271+
}
272+
273+
// Write writes data to the connection `chunkSize` bytes (or less) at a time.
274+
func (c *writeThrottledConn) Write(b []byte) (n int, err error) {
275+
for i := 0; i < len(b); i += c.chunkSize {
276+
n, err := c.Conn.Write(b[i:min(i+c.chunkSize, len(b))])
277+
if err != nil {
278+
return i + n, err
279+
}
280+
}
281+
return len(b), nil
282+
}

0 commit comments

Comments
 (0)