add downstream mTLS integration test cases (#4234)

* integration test config: add downstream mTLS routes

Add two new CA certificates for use with downstream mTLS tests, and a
client certificate/key pair issued by each CA.

Add a few routes to the policy template that require a client CA. Update
the generated output configurations.

(based on commit ed63a6a6e7)

* add downstream mTLS integration test cases

These are modeled after the tests added to v0.17 in 83957a9, but here
the expected behavior is that requests with an invalid client
certificate will receive a 495 response only after authentication.
This commit is contained in:
Kenneth Jenkins 2023-06-13 10:25:21 -07:00 committed by GitHub
parent d96ca0611a
commit 3ebee1159c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 335 additions and 6 deletions

View file

@ -12,8 +12,10 @@ import (
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/integration/flows"
"github.com/pomerium/pomerium/internal/httputil"
)
func TestQueryStringParams(t *testing.T) {
@ -339,3 +341,137 @@ func TestLoadBalancer(t *testing.T) {
distribution)
})
}
func TestDownstreamClientCA(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Minute*10)
defer clearTimeout()
t.Run("no client cert", func(t *testing.T) {
res, err := flows.Authenticate(ctx, getClient(t),
mustParseURL("https://client-cert-required.localhost.pomerium.io/"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
t.Run("untrusted client cert", func(t *testing.T) {
// Configure an http.Client with an untrusted client certificate.
cert := loadCertificate(t, "downstream-2-client")
client, transport := getClientWithTransport(t)
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-required.localhost.pomerium.io/"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
t.Run("valid client cert", func(t *testing.T) {
// Configure an http.Client with a trusted client certificate.
cert := loadCertificate(t, "downstream-1-client")
client, transport := getClientWithTransport(t)
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-required.localhost.pomerium.io/"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
defer res.Body.Close()
var result struct {
Path string `json:"path"`
}
err = json.NewDecoder(res.Body).Decode(&result)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, "/", result.Path)
})
}
func TestMultipleDownstreamClientCAs(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Minute*10)
defer clearTimeout()
// Initializes a new http.Client with the given certificate.
newClientWithCert := func(certName string) *http.Client {
cert := loadCertificate(t, certName)
client, transport := getClientWithTransport(t)
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
return client
}
// Asserts that we get a successful JSON response from the httpdetails
// service, matching the given path.
assertOK := func(res *http.Response, err error, path string) {
require.NoError(t, err, "unexpected http error")
defer res.Body.Close()
var result struct {
Path string `json:"path"`
}
err = json.NewDecoder(res.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, path, result.Path)
}
t.Run("cert1", func(t *testing.T) {
client := newClientWithCert("downstream-1-client")
// With cert1, we should get a valid response for the /ca1 path.
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-overlap.localhost.pomerium.io/ca1"),
flows.WithEmail("user1@dogs.test"))
assertOK(res, err, "/ca1")
// With cert1, we should get an HTML error page for the /ca2 path.
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://client-cert-overlap.localhost.pomerium.io/ca2", nil)
require.NoError(t, err)
res, err = client.Do(req)
require.NoError(t, err, "unexpected http error")
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
t.Run("cert2", func(t *testing.T) {
client := newClientWithCert("downstream-2-client")
// With cert2, we should get an HTML error page for the /ca1 path
// (after login).
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-overlap.localhost.pomerium.io/ca1"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
// With cert2, we should get a valid response for the /ca2 path.
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://client-cert-overlap.localhost.pomerium.io/ca2", nil)
require.NoError(t, err, "unexpected http error")
res, err = client.Do(req)
assertOK(res, err, "/ca2")
})
t.Run("no cert", func(t *testing.T) {
client := getClient(t)
// Without a client certificate, both paths should return an HTML error
// page (after login).
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-overlap.localhost.pomerium.io/ca1"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://client-cert-overlap.localhost.pomerium.io/ca2", nil)
require.NoError(t, err)
res, err = client.Do(req)
require.NoError(t, err, "unexpected http error")
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
}