mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-16 00:06:31 +02:00
authorize: incorporate mTLS validation from Envoy
Configure Envoy to validate client certificates, using the union of all relevant client CA bundles (that is, a bundle of the main client CA setting together with all per-route client CAs). Pass the validation status from Envoy through to the authorize service, by configuring Envoy to use the newly-added SetClientCertificateMetadata filter, and by also adding the relevant metadata namespace to the ExtAuthz configuration. Remove the existing 'include_peer_certificate' setting from the ExtAuthz configuration, as the metadata from the Lua filter will include the full certificate chain (when it validates successfully by Envoy). Update policy evaluation to consider the validation status from Envoy, in addition to its own certificate chain validation. (Policy evaluation cannot rely solely on the Envoy validation status while we still support the per-route client CA setting.)
This commit is contained in:
parent
8e4f728c11
commit
36ba83a6b0
11 changed files with 409 additions and 86 deletions
|
@ -32,13 +32,13 @@ type Request struct {
|
||||||
|
|
||||||
// RequestHTTP is the HTTP field in the request.
|
// RequestHTTP is the HTTP field in the request.
|
||||||
type RequestHTTP struct {
|
type RequestHTTP struct {
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Headers map[string]string `json:"headers"`
|
Headers map[string]string `json:"headers"`
|
||||||
ClientCertificate string `json:"client_certificate"`
|
ClientCertificate ClientCertificateInfo `json:"client_certificate"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequestHTTP creates a new RequestHTTP.
|
// NewRequestHTTP creates a new RequestHTTP.
|
||||||
|
@ -46,7 +46,7 @@ func NewRequestHTTP(
|
||||||
method string,
|
method string,
|
||||||
requestURL url.URL,
|
requestURL url.URL,
|
||||||
headers map[string]string,
|
headers map[string]string,
|
||||||
rawClientCertificate string,
|
clientCertificate ClientCertificateInfo,
|
||||||
ip string,
|
ip string,
|
||||||
) RequestHTTP {
|
) RequestHTTP {
|
||||||
return RequestHTTP{
|
return RequestHTTP{
|
||||||
|
@ -55,11 +55,33 @@ func NewRequestHTTP(
|
||||||
Path: requestURL.Path,
|
Path: requestURL.Path,
|
||||||
URL: requestURL.String(),
|
URL: requestURL.String(),
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
ClientCertificate: rawClientCertificate,
|
ClientCertificate: clientCertificate,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientCertificateInfo contains information about the certificate presented
|
||||||
|
// by the client (if any).
|
||||||
|
type ClientCertificateInfo struct {
|
||||||
|
// Presented is true if the client presented any certificate at all.
|
||||||
|
Presented bool `json:"presented"`
|
||||||
|
|
||||||
|
// Validated is true if the client presented a valid certificate with a
|
||||||
|
// trust chain rooted at any of the CAs configured within the Envoy
|
||||||
|
// listener. If any routes define a tls_downstream_client_ca, additional
|
||||||
|
// validation is required (for all routes).
|
||||||
|
Validated bool `json:"validated"`
|
||||||
|
|
||||||
|
// Leaf contains the leaf client certificate, provided that the certificate
|
||||||
|
// validated successfully.
|
||||||
|
Leaf string `json:"leaf,omitempty"`
|
||||||
|
|
||||||
|
// Intermediates contains the remainder of the client certificate chain as
|
||||||
|
// it was originally presented by the client, provided that the client
|
||||||
|
// certificate validated successfully.
|
||||||
|
Intermediates string `json:"intermediates,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// RequestSession is the session field in the request.
|
// RequestSession is the session field in the request.
|
||||||
type RequestSession struct {
|
type RequestSession struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -193,7 +215,8 @@ func (e *Evaluator) evaluatePolicy(ctx context.Context, req *Request) (*PolicyRe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidClientCertificate, err := isValidClientCertificate(clientCA, req.HTTP.ClientCertificate)
|
isValidClientCertificate, err :=
|
||||||
|
isValidClientCertificate(clientCA, req.HTTP.ClientCertificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("authorize: error validating client certificate: %w", err)
|
return nil, fmt.Errorf("authorize: error validating client certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,12 @@ func TestEvaluator(t *testing.T) {
|
||||||
WithPolicies(policies),
|
WithPolicies(policies),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validCertInfo := ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
Leaf: testValidCert,
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("client certificate", func(t *testing.T) {
|
t.Run("client certificate", func(t *testing.T) {
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
res, err := eval(t, options, nil, &Request{
|
res, err := eval(t, options, nil, &Request{
|
||||||
|
@ -128,7 +134,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
res, err := eval(t, options, nil, &Request{
|
res, err := eval(t, options, nil, &Request{
|
||||||
Policy: &policies[0],
|
Policy: &policies[0],
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -154,7 +160,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -179,7 +185,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -206,7 +212,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -230,7 +236,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -254,7 +260,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -285,7 +291,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -310,7 +316,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -334,7 +340,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -363,7 +369,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -386,7 +392,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -423,7 +429,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "https://from.example.com",
|
URL: "https://from.example.com",
|
||||||
ClientCertificate: testValidCert,
|
ClientCertificate: validCertInfo,
|
||||||
Headers: tc.src,
|
Headers: tc.src,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -439,7 +445,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
*mustParseURL("https://from.example.com/"),
|
*mustParseURL("https://from.example.com/"),
|
||||||
nil,
|
nil,
|
||||||
testValidCert,
|
validCertInfo,
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
@ -453,7 +459,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
"POST",
|
"POST",
|
||||||
*mustParseURL("https://from.example.com/test"),
|
*mustParseURL("https://from.example.com/test"),
|
||||||
nil,
|
nil,
|
||||||
testValidCert,
|
validCertInfo,
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,14 +13,15 @@ import (
|
||||||
|
|
||||||
var isValidClientCertificateCache, _ = lru.New2Q[[2]string, bool](100)
|
var isValidClientCertificateCache, _ = lru.New2Q[[2]string, bool](100)
|
||||||
|
|
||||||
func isValidClientCertificate(ca, cert string) (bool, error) {
|
func isValidClientCertificate(ca string, certInfo ClientCertificateInfo) (bool, error) {
|
||||||
// when ca is the empty string, client certificates are always accepted
|
// when ca is the empty string, client certificates are not required
|
||||||
if ca == "" {
|
if ca == "" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// when cert is the empty string, no client certificate was supplied
|
cert := certInfo.Leaf
|
||||||
if cert == "" {
|
|
||||||
|
if !certInfo.Validated || cert == "" {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,27 +95,48 @@ Y+E5W+FKfIBv9yvdNBYZsL6IZ0Yh1ctKwB5gnajO8+swx5BeaCIbBrCtOBSB
|
||||||
|
|
||||||
func Test_isValidClientCertificate(t *testing.T) {
|
func Test_isValidClientCertificate(t *testing.T) {
|
||||||
t.Run("no ca", func(t *testing.T) {
|
t.Run("no ca", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate("", "WHATEVER!")
|
valid, err := isValidClientCertificate("", ClientCertificateInfo{Leaf: "WHATEVER!"})
|
||||||
assert.NoError(t, err, "should not return an error")
|
assert.NoError(t, err, "should not return an error")
|
||||||
assert.True(t, valid, "should return true")
|
assert.True(t, valid, "should return true")
|
||||||
})
|
})
|
||||||
t.Run("no cert", func(t *testing.T) {
|
t.Run("no cert", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate(testCA, "")
|
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{})
|
||||||
assert.NoError(t, err, "should not return an error")
|
assert.NoError(t, err, "should not return an error")
|
||||||
assert.False(t, valid, "should return false")
|
assert.False(t, valid, "should return false")
|
||||||
})
|
})
|
||||||
t.Run("valid cert", func(t *testing.T) {
|
t.Run("valid cert", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate(testCA, testValidCert)
|
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
Leaf: testValidCert,
|
||||||
|
})
|
||||||
assert.NoError(t, err, "should not return an error")
|
assert.NoError(t, err, "should not return an error")
|
||||||
assert.True(t, valid, "should return true")
|
assert.True(t, valid, "should return true")
|
||||||
})
|
})
|
||||||
|
t.Run("cert not externally validated", func(t *testing.T) {
|
||||||
|
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: false,
|
||||||
|
Leaf: testValidCert,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "should not return an error")
|
||||||
|
assert.False(t, valid, "should return false")
|
||||||
|
})
|
||||||
t.Run("unsigned cert", func(t *testing.T) {
|
t.Run("unsigned cert", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate(testCA, testUnsignedCert)
|
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
Leaf: testUnsignedCert,
|
||||||
|
})
|
||||||
assert.NoError(t, err, "should not return an error")
|
assert.NoError(t, err, "should not return an error")
|
||||||
assert.False(t, valid, "should return false")
|
assert.False(t, valid, "should return false")
|
||||||
})
|
})
|
||||||
t.Run("not a cert", func(t *testing.T) {
|
t.Run("not a cert", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate(testCA, "WHATEVER!")
|
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
Leaf: "WHATEVER!",
|
||||||
|
})
|
||||||
assert.Error(t, err, "should return an error")
|
assert.Error(t, err, "should return an error")
|
||||||
assert.False(t, valid, "should return false")
|
assert.False(t, valid, "should return false")
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,12 +2,14 @@ package authorize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/pem"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
|
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authorize/evaluator"
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
|
@ -60,7 +62,7 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
|
||||||
u, _ = a.getDataBrokerUser(ctx, s.GetUserId()) // ignore any missing user error
|
u, _ = a.getDataBrokerUser(ctx, s.GetUserId()) // ignore any missing user error
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := a.getEvaluatorRequestFromCheckRequest(in, sessionState)
|
req, err := a.getEvaluatorRequestFromCheckRequest(ctx, in, sessionState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(ctx).Err(err).Msg("error building evaluator request")
|
log.Warn(ctx).Err(err).Msg("error building evaluator request")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -89,18 +91,22 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authorize) getEvaluatorRequestFromCheckRequest(
|
func (a *Authorize) getEvaluatorRequestFromCheckRequest(
|
||||||
|
ctx context.Context,
|
||||||
in *envoy_service_auth_v3.CheckRequest,
|
in *envoy_service_auth_v3.CheckRequest,
|
||||||
sessionState *sessions.State,
|
sessionState *sessions.State,
|
||||||
) (*evaluator.Request, error) {
|
) (*evaluator.Request, error) {
|
||||||
requestURL := getCheckRequestURL(in)
|
requestURL := getCheckRequestURL(in)
|
||||||
|
attrs := in.GetAttributes()
|
||||||
|
clientCertMetadata :=
|
||||||
|
attrs.GetMetadataContext().GetFilterMetadata()["com.pomerium.client-certificate-info"]
|
||||||
req := &evaluator.Request{
|
req := &evaluator.Request{
|
||||||
IsInternal: envoyconfig.ExtAuthzContextExtensionsIsInternal(in.GetAttributes().GetContextExtensions()),
|
IsInternal: envoyconfig.ExtAuthzContextExtensionsIsInternal(attrs.GetContextExtensions()),
|
||||||
HTTP: evaluator.NewRequestHTTP(
|
HTTP: evaluator.NewRequestHTTP(
|
||||||
in.GetAttributes().GetRequest().GetHttp().GetMethod(),
|
attrs.GetRequest().GetHttp().GetMethod(),
|
||||||
requestURL,
|
requestURL,
|
||||||
getCheckRequestHeaders(in),
|
getCheckRequestHeaders(in),
|
||||||
getPeerCertificate(in),
|
getClientCertificateInfo(ctx, clientCertMetadata),
|
||||||
in.GetAttributes().GetSource().GetAddress().GetSocketAddress().GetAddress(),
|
attrs.GetSource().GetAddress().GetSocketAddress().GetAddress(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
if sessionState != nil {
|
if sessionState != nil {
|
||||||
|
@ -108,7 +114,7 @@ func (a *Authorize) getEvaluatorRequestFromCheckRequest(
|
||||||
ID: sessionState.ID,
|
ID: sessionState.ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.Policy = a.getMatchingPolicy(envoyconfig.ExtAuthzContextExtensionsRouteID(in.Attributes.GetContextExtensions()))
|
req.Policy = a.getMatchingPolicy(envoyconfig.ExtAuthzContextExtensionsRouteID(attrs.GetContextExtensions()))
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,9 +176,38 @@ func getCheckRequestURL(req *envoy_service_auth_v3.CheckRequest) url.URL {
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPeerCertificate gets the PEM-encoded peer certificate from the check request
|
// getClientCertificateInfo translates from the client certificate Envoy
|
||||||
func getPeerCertificate(in *envoy_service_auth_v3.CheckRequest) string {
|
// metadata to the ClientCertificateInfo type.
|
||||||
// ignore the error as we will just return the empty string in that case
|
func getClientCertificateInfo(
|
||||||
cert, _ := url.QueryUnescape(in.GetAttributes().GetSource().GetCertificate())
|
ctx context.Context, metadata *structpb.Struct,
|
||||||
return cert
|
) evaluator.ClientCertificateInfo {
|
||||||
|
var c evaluator.ClientCertificateInfo
|
||||||
|
if metadata == nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
c.Presented = metadata.Fields["presented"].GetBoolValue()
|
||||||
|
c.Validated = metadata.Fields["validated"].GetBoolValue()
|
||||||
|
escapedChain := metadata.Fields["chain"].GetStringValue()
|
||||||
|
if escapedChain == "" {
|
||||||
|
// No validated client certificate.
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, err := url.QueryUnescape(escapedChain)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(ctx).Str("chain", escapedChain).Err(err).
|
||||||
|
Msg(`received unexpected client certificate "chain" value`)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the chain into the leaf and any intermediate certificates.
|
||||||
|
p, rest := pem.Decode([]byte(chain))
|
||||||
|
if p == nil {
|
||||||
|
log.Warn(ctx).Str("chain", escapedChain).
|
||||||
|
Msg(`received unexpected client certificate "chain" value (no PEM block found)`)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
c.Leaf = string(pem.EncodeToMemory(p))
|
||||||
|
c.Intermediates = string(rest)
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
package authorize
|
package authorize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
|
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authorize/evaluator"
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/atomicutil"
|
"github.com/pomerium/pomerium/internal/atomicutil"
|
||||||
"github.com/pomerium/pomerium/internal/sessions"
|
"github.com/pomerium/pomerium/internal/sessions"
|
||||||
|
"github.com/pomerium/pomerium/internal/testutil"
|
||||||
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,12 +58,9 @@ func Test_getEvaluatorRequest(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
actual, err := a.getEvaluatorRequestFromCheckRequest(
|
actual, err := a.getEvaluatorRequestFromCheckRequest(context.Background(),
|
||||||
&envoy_service_auth_v3.CheckRequest{
|
&envoy_service_auth_v3.CheckRequest{
|
||||||
Attributes: &envoy_service_auth_v3.AttributeContext{
|
Attributes: &envoy_service_auth_v3.AttributeContext{
|
||||||
Source: &envoy_service_auth_v3.AttributeContext_Peer{
|
|
||||||
Certificate: url.QueryEscape(certPEM),
|
|
||||||
},
|
|
||||||
Request: &envoy_service_auth_v3.AttributeContext_Request{
|
Request: &envoy_service_auth_v3.AttributeContext_Request{
|
||||||
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
|
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
|
||||||
Id: "id-1234",
|
Id: "id-1234",
|
||||||
|
@ -73,6 +75,17 @@ func Test_getEvaluatorRequest(t *testing.T) {
|
||||||
Body: "BODY",
|
Body: "BODY",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MetadataContext: &envoy_config_core_v3.Metadata{
|
||||||
|
FilterMetadata: map[string]*structpb.Struct{
|
||||||
|
"com.pomerium.client-certificate-info": &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"presented": structpb.NewBoolValue(true),
|
||||||
|
"validated": structpb.NewBoolValue(true),
|
||||||
|
"chain": structpb.NewStringValue(url.QueryEscape(certPEM)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&sessions.State{
|
&sessions.State{
|
||||||
|
@ -92,7 +105,12 @@ func Test_getEvaluatorRequest(t *testing.T) {
|
||||||
"Accept": "text/html",
|
"Accept": "text/html",
|
||||||
"X-Forwarded-Proto": "https",
|
"X-Forwarded-Proto": "https",
|
||||||
},
|
},
|
||||||
certPEM,
|
evaluator.ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
Leaf: certPEM[1:] + "\n",
|
||||||
|
Intermediates: "",
|
||||||
|
},
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -110,27 +128,25 @@ func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
actual, err := a.getEvaluatorRequestFromCheckRequest(&envoy_service_auth_v3.CheckRequest{
|
actual, err := a.getEvaluatorRequestFromCheckRequest(context.Background(),
|
||||||
Attributes: &envoy_service_auth_v3.AttributeContext{
|
&envoy_service_auth_v3.CheckRequest{
|
||||||
Source: &envoy_service_auth_v3.AttributeContext_Peer{
|
Attributes: &envoy_service_auth_v3.AttributeContext{
|
||||||
Certificate: url.QueryEscape(certPEM),
|
Request: &envoy_service_auth_v3.AttributeContext_Request{
|
||||||
},
|
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
|
||||||
Request: &envoy_service_auth_v3.AttributeContext_Request{
|
Id: "id-1234",
|
||||||
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
|
Method: http.MethodGet,
|
||||||
Id: "id-1234",
|
Headers: map[string]string{
|
||||||
Method: http.MethodGet,
|
"accept": "text/html",
|
||||||
Headers: map[string]string{
|
"x-forwarded-proto": "https",
|
||||||
"accept": "text/html",
|
},
|
||||||
"x-forwarded-proto": "https",
|
Path: "/some/path?qs=1",
|
||||||
|
Host: "example.com:80",
|
||||||
|
Scheme: "http",
|
||||||
|
Body: "BODY",
|
||||||
},
|
},
|
||||||
Path: "/some/path?qs=1",
|
|
||||||
Host: "example.com:80",
|
|
||||||
Scheme: "http",
|
|
||||||
Body: "BODY",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}, nil)
|
||||||
}, nil)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expect := &evaluator.Request{
|
expect := &evaluator.Request{
|
||||||
Policy: &a.currentOptions.Load().Policies[0],
|
Policy: &a.currentOptions.Load().Policies[0],
|
||||||
|
@ -142,13 +158,144 @@ func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
|
||||||
"Accept": "text/html",
|
"Accept": "text/html",
|
||||||
"X-Forwarded-Proto": "https",
|
"X-Forwarded-Proto": "https",
|
||||||
},
|
},
|
||||||
certPEM,
|
evaluator.ClientCertificateInfo{},
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
assert.Equal(t, expect, actual)
|
assert.Equal(t, expect, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_getClientCertificateInfo(t *testing.T) {
|
||||||
|
const leafPEM = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBZTCCAQugAwIBAgICEAEwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPSW50ZXJt
|
||||||
|
ZWRpYXRlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMB8x
|
||||||
|
HTAbBgNVBAMTFENsaWVudCBjZXJ0aWZpY2F0ZSAxMFkwEwYHKoZIzj0CAQYIKoZI
|
||||||
|
zj0DAQcDQgAESly1cwEbcxaJBl6qAhrX1k7vejTFNE2dEbrTMpUYMl86GEWdsDYN
|
||||||
|
KSa/1wZCowPy82gPGjfAU90odkqJOusCQqM4MDYwEwYDVR0lBAwwCgYIKwYBBQUH
|
||||||
|
AwIwHwYDVR0jBBgwFoAU6Qb7nEl2XHKpf/QLL6PENsHFqbowCgYIKoZIzj0EAwID
|
||||||
|
SAAwRQIgXREMUz81pYwJCMLGcV0ApaXIUap1V5n1N4VhyAGxGLYCIQC8p/LwoSgu
|
||||||
|
71H3/nCi5MxsECsvVtsmHIfwXt0wulQ1TA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
const intermediatePEM = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBYzCCAQigAwIBAgICEAEwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHUm9vdCBD
|
||||||
|
QTAiGA8wMDAxMDEwMTAwMDAwMFoYDzAwMDEwMTAxMDAwMDAwWjAaMRgwFgYDVQQD
|
||||||
|
Ew9JbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATYaTr9
|
||||||
|
uH4LpEp541/2SlKrdQZwNns+NHY/ftm++NhMDUn+izzNbPZ5aPT6VBs4Q6vbgfkK
|
||||||
|
kDaBpaKzb+uOT+o1o0IwQDAdBgNVHQ4EFgQU6Qb7nEl2XHKpf/QLL6PENsHFqbow
|
||||||
|
HwYDVR0jBBgwFoAUiQ3r61y+vxDn6PMWZrpISr67HiQwCgYIKoZIzj0EAwIDSQAw
|
||||||
|
RgIhAMvdURs28uib2QwSMnqJjKasMb30yrSJvTiSU+lcg97/AiEA+6GpioM0c221
|
||||||
|
n/XNKVYEkPmeXHRoz9ZuVDnSfXKJoHE=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
const rootPEM = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBNzCB36ADAgECAgIQADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdSb290IENB
|
||||||
|
MCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBIxEDAOBgNVBAMT
|
||||||
|
B1Jvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS6q0mTvm29xasq7Lwk
|
||||||
|
aRGb2S/LkQFsAwaCXohSNvonCQHRMCRvA1IrQGk/oyBS5qrDoD9/7xkcVYHuTv5D
|
||||||
|
CbtuoyEwHzAdBgNVHQ4EFgQUiQ3r61y+vxDn6PMWZrpISr67HiQwCgYIKoZIzj0E
|
||||||
|
AwIDRwAwRAIgF1ux0ridbN+bo0E3TTcNY8Xfva7yquYRMmEkfbGvSb0CIDqK80B+
|
||||||
|
fYCZHo3CID0gRSemaQ/jYMgyeBFrHIr6icZh
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
label string
|
||||||
|
presented bool
|
||||||
|
validated bool
|
||||||
|
chain string
|
||||||
|
expected evaluator.ClientCertificateInfo
|
||||||
|
expectedLog string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"not presented",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
evaluator.ClientCertificateInfo{},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"presented but invalid",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
evaluator.ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"validated",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
url.QueryEscape(leafPEM),
|
||||||
|
evaluator.ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
Leaf: leafPEM,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"validated with intermediates",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
url.QueryEscape(leafPEM + intermediatePEM + rootPEM),
|
||||||
|
evaluator.ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
Leaf: leafPEM,
|
||||||
|
Intermediates: intermediatePEM + rootPEM,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid chain URL encoding",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"invalid%URL%encoding",
|
||||||
|
evaluator.ClientCertificateInfo{},
|
||||||
|
`{"level":"warn","chain":"invalid%URL%encoding","error":"invalid URL escape \"%UR\"","message":"received unexpected client certificate \"chain\" value"}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid chain PEM encoding",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"not valid PEM data",
|
||||||
|
evaluator.ClientCertificateInfo{
|
||||||
|
Presented: true,
|
||||||
|
Validated: true,
|
||||||
|
},
|
||||||
|
`{"level":"warn","chain":"not valid PEM data","message":"received unexpected client certificate \"chain\" value (no PEM block found)"}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var logOutput bytes.Buffer
|
||||||
|
zl := zerolog.New(&logOutput)
|
||||||
|
testutil.SetLogger(t, &zl)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
for i := range cases {
|
||||||
|
c := &cases[i]
|
||||||
|
logOutput.Reset()
|
||||||
|
t.Run(c.label, func(t *testing.T) {
|
||||||
|
metadata := &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"presented": structpb.NewBoolValue(c.presented),
|
||||||
|
"validated": structpb.NewBoolValue(c.validated),
|
||||||
|
"chain": structpb.NewStringValue(c.chain),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
info := getClientCertificateInfo(ctx, metadata)
|
||||||
|
assert.Equal(t, c.expected, info)
|
||||||
|
assert.Equal(t, c.expectedLog, logOutput.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type mockDataBrokerServiceClient struct {
|
type mockDataBrokerServiceClient struct {
|
||||||
databroker.DataBrokerServiceClient
|
databroker.DataBrokerServiceClient
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ func ExtAuthzFilter(grpcClientTimeout *durationpb.Duration) *envoy_extensions_fi
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IncludePeerCertificate: true,
|
MetadataContextNamespaces: []string{"com.pomerium.client-certificate-info"},
|
||||||
TransportApiVersion: envoy_config_core_v3.ApiVersion_V3,
|
TransportApiVersion: envoy_config_core_v3.ApiVersion_V3,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package envoyconfig
|
package envoyconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -261,6 +262,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
|
||||||
|
|
||||||
filters := []*envoy_http_connection_manager.HttpFilter{
|
filters := []*envoy_http_connection_manager.HttpFilter{
|
||||||
LuaFilter(luascripts.RemoveImpersonateHeaders),
|
LuaFilter(luascripts.RemoveImpersonateHeaders),
|
||||||
|
LuaFilter(luascripts.SetClientCertificateMetadata),
|
||||||
ExtAuthzFilter(grpcClientTimeout),
|
ExtAuthzFilter(grpcClientTimeout),
|
||||||
LuaFilter(luascripts.ExtAuthzSetCookie),
|
LuaFilter(luascripts.ExtAuthzSetCookie),
|
||||||
LuaFilter(luascripts.CleanUpstream),
|
LuaFilter(luascripts.CleanUpstream),
|
||||||
|
@ -540,23 +542,15 @@ func (b *Builder) buildDownstreamValidationContext(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
) *envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext {
|
) *envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext {
|
||||||
needsClientCert := false
|
clientCA := clientCABundle(ctx, cfg)
|
||||||
if ca, _ := cfg.Options.GetClientCA(); len(ca) > 0 {
|
if len(clientCA) == 0 {
|
||||||
needsClientCert = true
|
|
||||||
}
|
|
||||||
for _, p := range cfg.Options.GetAllPolicies() {
|
|
||||||
if p.TLSDownstreamClientCA != "" || p.TLSDownstreamClientCAFile != "" {
|
|
||||||
needsClientCert = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !needsClientCert {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// trusted_ca is left blank because we verify the client certificate in the authorize service
|
|
||||||
vc := &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
|
vc := &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
|
||||||
ValidationContext: &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
|
ValidationContext: &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
|
||||||
TrustChainVerification: envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext_ACCEPT_UNTRUSTED,
|
TrustChainVerification: envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext_ACCEPT_UNTRUSTED,
|
||||||
|
TrustedCa: b.filemgr.BytesDataSource("client-ca.pem", clientCA),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -574,6 +568,39 @@ func (b *Builder) buildDownstreamValidationContext(
|
||||||
return vc
|
return vc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clientCABundle returns a bundle of the globally configured client CA and any
|
||||||
|
// per-route client CAs.
|
||||||
|
func clientCABundle(ctx context.Context, cfg *config.Config) []byte {
|
||||||
|
var bundle bytes.Buffer
|
||||||
|
ca, _ := cfg.Options.GetClientCA()
|
||||||
|
addCAToBundle(&bundle, ca)
|
||||||
|
allPolicies := cfg.Options.GetAllPolicies()
|
||||||
|
for i := range allPolicies {
|
||||||
|
p := &allPolicies[i]
|
||||||
|
if p.TLSDownstreamClientCA == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ca, err := base64.StdEncoding.DecodeString(p.TLSDownstreamClientCA)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(ctx).Stringer("policy", p).Err(err).Msg("invalid client CA")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addCAToBundle(&bundle, ca)
|
||||||
|
}
|
||||||
|
return bundle.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCAToBundle(bundle *bytes.Buffer, ca []byte) {
|
||||||
|
if len(ca) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bundle.Write(ca)
|
||||||
|
// Make sure each CA is separated by a newline.
|
||||||
|
if ca[len(ca)-1] != '\n' {
|
||||||
|
bundle.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getAllRouteableHosts(options *config.Options, addr string) ([]string, error) {
|
func getAllRouteableHosts(options *config.Options, addr string) ([]string, error) {
|
||||||
allHosts := sets.NewSorted[string]()
|
allHosts := sets.NewSorted[string]()
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,9 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
|
||||||
func Test_buildDownstreamTLSContext(t *testing.T) {
|
func Test_buildDownstreamTLSContext(t *testing.T) {
|
||||||
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
|
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
|
||||||
|
|
||||||
|
cacheDir, _ := os.UserCacheDir()
|
||||||
|
clientCAFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "client-ca-3533485838304b593757424e3354425157494c4747433534384f474f3631364d5332554c3332485a483834334d50454c344a.pem")
|
||||||
|
|
||||||
t.Run("no-validation", func(t *testing.T) {
|
t.Run("no-validation", func(t *testing.T) {
|
||||||
downstreamTLSContext, err := b.buildDownstreamTLSContextMulti(context.Background(), &config.Config{Options: &config.Options{}}, nil)
|
downstreamTLSContext, err := b.buildDownstreamTLSContextMulti(context.Background(), &config.Config{Options: &config.Options{}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -95,7 +98,7 @@ func Test_buildDownstreamTLSContext(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("client-ca", func(t *testing.T) {
|
t.Run("client-ca", func(t *testing.T) {
|
||||||
downstreamTLSContext, err := b.buildDownstreamTLSContextMulti(context.Background(), &config.Config{Options: &config.Options{
|
downstreamTLSContext, err := b.buildDownstreamTLSContextMulti(context.Background(), &config.Config{Options: &config.Options{
|
||||||
ClientCA: "TEST",
|
ClientCA: "VEVTVAo=", // "TEST\n" (with a trailing newline)
|
||||||
}}, nil)
|
}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testutil.AssertProtoJSONEqual(t, `{
|
testutil.AssertProtoJSONEqual(t, `{
|
||||||
|
@ -113,7 +116,10 @@ func Test_buildDownstreamTLSContext(t *testing.T) {
|
||||||
},
|
},
|
||||||
"alpnProtocols": ["h2", "http/1.1"],
|
"alpnProtocols": ["h2", "http/1.1"],
|
||||||
"validationContext": {
|
"validationContext": {
|
||||||
"trustChainVerification": "ACCEPT_UNTRUSTED"
|
"trustChainVerification": "ACCEPT_UNTRUSTED",
|
||||||
|
"trustedCa": {
|
||||||
|
"filename": "`+clientCAFileName+`"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, downstreamTLSContext)
|
}`, downstreamTLSContext)
|
||||||
|
@ -123,7 +129,7 @@ func Test_buildDownstreamTLSContext(t *testing.T) {
|
||||||
Policies: []config.Policy{
|
Policies: []config.Policy{
|
||||||
{
|
{
|
||||||
From: "https://a.example.com:1234",
|
From: "https://a.example.com:1234",
|
||||||
TLSDownstreamClientCA: "TEST",
|
TLSDownstreamClientCA: "VEVTVA==", // "TEST" (no trailing newline)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}, nil)
|
}}, nil)
|
||||||
|
@ -144,7 +150,10 @@ func Test_buildDownstreamTLSContext(t *testing.T) {
|
||||||
},
|
},
|
||||||
"alpnProtocols": ["h2", "http/1.1"],
|
"alpnProtocols": ["h2", "http/1.1"],
|
||||||
"validationContext": {
|
"validationContext": {
|
||||||
"trustChainVerification": "ACCEPT_UNTRUSTED"
|
"trustChainVerification": "ACCEPT_UNTRUSTED",
|
||||||
|
"trustedCa": {
|
||||||
|
"filename": "`+clientCAFileName+`"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, downstreamTLSContext)
|
}`, downstreamTLSContext)
|
||||||
|
@ -201,6 +210,31 @@ func Test_buildDownstreamTLSContext(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_clientCABundle(t *testing.T) {
|
||||||
|
// Make sure multiple bundled CAs are separated by newlines.
|
||||||
|
clientCA1 := []byte("client CA 1")
|
||||||
|
clientCA2 := []byte("client CA 2")
|
||||||
|
clientCA3 := []byte("client CA 3")
|
||||||
|
|
||||||
|
b64 := base64.StdEncoding.EncodeToString
|
||||||
|
cfg := &config.Config{Options: &config.Options{
|
||||||
|
ClientCA: b64(clientCA3),
|
||||||
|
Policies: []config.Policy{
|
||||||
|
{
|
||||||
|
From: "https://foo.example.com",
|
||||||
|
TLSDownstreamClientCA: b64(clientCA2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: "https://bar.example.com",
|
||||||
|
TLSDownstreamClientCA: b64(clientCA1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
expected := []byte("client CA 3\nclient CA 2\nclient CA 1\n")
|
||||||
|
actual := clientCABundle(context.Background(), cfg)
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
func Test_getAllDomains(t *testing.T) {
|
func Test_getAllDomains(t *testing.T) {
|
||||||
cert, err := cryptutil.GenerateCertificate(nil, "*.unknown.example.com")
|
cert, err := cryptutil.GenerateCertificate(nil, "*.unknown.example.com")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -33,6 +33,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.http.lua",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
|
||||||
|
"defaultSourceCode": {
|
||||||
|
"inlineString": "function envoy_on_request(request_handle)\n local metadata = request_handle:streamInfo():dynamicMetadata()\n local ssl = request_handle:streamInfo():downstreamSslConnection()\n metadata:set(\"com.pomerium.client-certificate-info\", \"presented\",\n ssl:peerCertificatePresented())\n local validated = ssl:peerCertificateValidated()\n metadata:set(\"com.pomerium.client-certificate-info\", \"validated\", validated)\n if validated then\n metadata:set(\"com.pomerium.client-certificate-info\", \"chain\",\n ssl:urlEncodedPemEncodedPeerCertificateChain())\n end\nend\n\nfunction envoy_on_response(response_handle) end\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "envoy.filters.http.ext_authz",
|
"name": "envoy.filters.http.ext_authz",
|
||||||
"typedConfig": {
|
"typedConfig": {
|
||||||
|
@ -43,7 +52,9 @@
|
||||||
},
|
},
|
||||||
"timeout": "10s"
|
"timeout": "10s"
|
||||||
},
|
},
|
||||||
"includePeerCertificate": true,
|
"metadataContextNamespaces": [
|
||||||
|
"com.pomerium.client-certificate-info"
|
||||||
|
],
|
||||||
"statusOnError": {
|
"statusOnError": {
|
||||||
"code": "InternalServerError"
|
"code": "InternalServerError"
|
||||||
},
|
},
|
||||||
|
|
18
internal/testutil/log.go
Normal file
18
internal/testutil/log.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetLogger sets the given logger as the global logger for the remainder of
|
||||||
|
// the current test. Because the logger is global, this must not be called from
|
||||||
|
// parallel tests.
|
||||||
|
func SetLogger(t *testing.T, logger *zerolog.Logger) {
|
||||||
|
originalLogger := log.Logger()
|
||||||
|
t.Cleanup(func() { log.SetLogger(originalLogger) })
|
||||||
|
log.SetLogger(logger)
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue