From 3286c398221dd199d6c767a1758c6d981a455e4c Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Fri, 11 Jan 2019 10:26:36 +0100 Subject: [PATCH] server: add authentication based on TLS * identifier: add TLSBased This is only the identifier, the server setup still has to be done. Note that it diverges a little from what was proposed in the issue: not every client cert needs to have a CN record -- so instead, we'll use whatever is the cert's subject as client identity. * Drive-by fix: identifier_test: don't use same package for TokenBased tests. * server: require and verify client cert for AuthenticationTLS * server: allow setting CA pool via --tls-ca-cert-file * server: expose new authentication via parameter * [nit] server: simplify getListenerForHTTPServer * server_test: use httptest for integration-y TLS tests * book/security: mention TLS authn with example Signed-off-by: Stephan Renatus --- cmd/run.go | 33 +++++- docs/book/security.md | 144 +++++++++++++++++++++++++ runtime/runtime.go | 5 + server/identifier/identifier_test.go | 8 +- server/identifier/testdata/.gitignore | 5 + server/identifier/testdata/cn-cert.pem | 18 ++++ server/identifier/testdata/gencerts.sh | 29 +++++ server/identifier/testdata/key.pem | 27 +++++ server/identifier/testdata/ou-cert.pem | 18 ++++ server/identifier/tls.go | 31 ++++++ server/identifier/tls_test.go | 83 ++++++++++++++ server/server.go | 27 +++-- server/server_test.go | 131 ++++++++++++++++++++++ server/testdata/.gitignore | 4 + server/testdata/ca.pem | 18 ++++ server/testdata/client-cert-2.pem | 17 +++ server/testdata/client-cert.pem | 17 +++ server/testdata/client-key-2.pem | 27 +++++ server/testdata/client-key.pem | 27 +++++ server/testdata/gencerts.sh | 35 ++++++ server/testdata/server-cert.pem | 18 ++++ server/testdata/server-key.pem | 27 +++++ 22 files changed, 735 insertions(+), 14 deletions(-) create mode 100644 server/identifier/testdata/.gitignore create mode 100644 server/identifier/testdata/cn-cert.pem create mode 100755 server/identifier/testdata/gencerts.sh create mode 100644 server/identifier/testdata/key.pem create mode 100644 server/identifier/testdata/ou-cert.pem create mode 100644 server/identifier/tls.go create mode 100644 server/identifier/tls_test.go create mode 100644 server/testdata/.gitignore create mode 100644 server/testdata/ca.pem create mode 100644 server/testdata/client-cert-2.pem create mode 100644 server/testdata/client-cert.pem create mode 100644 server/testdata/client-key-2.pem create mode 100644 server/testdata/client-key.pem create mode 100755 server/testdata/gencerts.sh create mode 100644 server/testdata/server-cert.pem create mode 100644 server/testdata/server-key.pem diff --git a/cmd/run.go b/cmd/run.go index 573e4ec6de..e384e9048b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -7,14 +7,17 @@ package cmd import ( "context" "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" "os" "path" + "github.com/spf13/cobra" + "github.com/open-policy-agent/opa/runtime" "github.com/open-policy-agent/opa/server" "github.com/open-policy-agent/opa/util" - "github.com/spf13/cobra" ) const ( @@ -26,14 +29,14 @@ const ( func init() { var serverMode bool - var tlsCertFile string - var tlsPrivateKeyFile string + var tlsCertFile, tlsPrivateKeyFile, tlsCACertFile string var ignore []string - authentication := util.NewEnumFlag("off", []string{"token", "off"}) + authentication := util.NewEnumFlag("off", []string{"token", "tls", "off"}) authenticationSchemes := map[string]server.AuthenticationScheme{ "token": server.AuthenticationToken, + "tls": server.AuthenticationTLS, "off": server.AuthenticationOff, } @@ -95,6 +98,15 @@ the data document with the following syntax: os.Exit(1) } + if tlsCACertFile != "" { + pool, err := loadCertPool(tlsCACertFile) + if err != nil { + fmt.Println("error:", err) + os.Exit(1) + } + params.CertPool = pool + } + params.Authentication = authenticationSchemes[authentication.String()] params.Authorization = authorizationScheme[authorization.String()] params.Certificate = cert @@ -137,6 +149,7 @@ the data document with the following syntax: runCommand.Flags().MarkDeprecated("server-diagnostics-buffer-size", "use decision logging instead") runCommand.Flags().StringVarP(&tlsCertFile, "tls-cert-file", "", "", "set path of TLS certificate file") runCommand.Flags().StringVarP(&tlsPrivateKeyFile, "tls-private-key-file", "", "", "set path of TLS private key file") + runCommand.Flags().StringVarP(&tlsCACertFile, "tls-ca-cert-file", "", "", "set path of TLS CA cert file") runCommand.Flags().VarP(authentication, "authentication", "", "set authentication scheme") runCommand.Flags().VarP(authorization, "authorization", "", "set authorization scheme") runCommand.Flags().VarP(logLevel, "log-level", "l", "set log level") @@ -177,3 +190,15 @@ func loadCertificate(tlsCertFile, tlsPrivateKeyFile string) (*tls.Certificate, e return nil, nil } + +func loadCertPool(tlsCACertFile string) (*x509.CertPool, error) { + caCertPEM, err := ioutil.ReadFile(tlsCACertFile) + if err != nil { + return nil, fmt.Errorf("read CA cert file: %v", err) + } + pool := x509.NewCertPool() + if ok := pool.AppendCertsFromPEM(caCertPEM); !ok { + return nil, fmt.Errorf("failed to parse CA cert %q", tlsCACertFile) + } + return pool, nil +} diff --git a/docs/book/security.md b/docs/book/security.md index 92513afe4e..f7ad1f49dc 100644 --- a/docs/book/security.md +++ b/docs/book/security.md @@ -23,6 +23,13 @@ startup: OPA will exit immediately with a non-zero status code if only one of these flags is specified. +Note that for using TLS-based authentication, a CA cert file can be provided: + +- ``--tls-ca-cert-file=`` specifies the path of the file containing the CA cert. + +If provided, it will be used to validate clients' TLS certificates when using TLS +authentication (see below). + By default, OPA ignores insecure HTTP connections when TLS is enabled. To allow insecure HTTP connections in addition to HTTPS connections, provide another listening address with `--addr`. For example: @@ -98,6 +105,17 @@ and provide to the authorization handler. When you use the `token` authentication, you must configure an authorization policy that checks the tokens. If the client does not supply a Bearer token, the `input.identity` value will be undefined when the authorization policy is evaluated. +- Client TLS certificates: Client TLS authentication is enabled by starting +OPA with ``--authentication=tls``. When this authentication mode is enabled, +OPA will require all clients to provide a client certificate. It is verified +against the CA certificate(s) provided via `--tls-ca-cert-path`. Upon successful +verification, the `input.identity` value is set to the TLS certificate's +subject. + + Note that TLS authentication does not disable non-HTTPS listeners. To ensure + that all your communication is secured, it should be paired with an + authorization policy (see below) that at least requires the client identity + (`input.identity`) to _be set_. For authorization, OPA relies on policy written in Rego. Authorization is enabled by starting OPA with ``--authorization=basic``. @@ -198,6 +216,8 @@ HTTP/1.1 200 OK Content-Type: application/json ``` +### Token-based Authentication Example + When Bearer tokens are used for authentication, the policy should at minimum validate the identity: @@ -276,3 +296,127 @@ identity_rights[right] { # Right is in the identity_rights set if... right = rights[role] # Role has rights defined. } ``` + +### TLS-based Authentication Example + +To set up authentication based on TLS, we will need three certificates: + +1. the CA cert (self-signed), +2. the server cert (signed by the CA), and +3. the client cert (signed by the CA). + +These are example invocations using `openssl`. +Don't use these in production, the key sizes are only good for demonstration purposes. + +Note that we're creating an extra client, which has a certificate signed by the proper +CA, but will later be used to illustrate the authorization policy. + +```bash +# CA +openssl genrsa -out ca-key.pem 2048 +openssl req -x509 -new -nodes -key ca-key.pem -days 1000 -out ca.pem -subj "/CN=my-ca" + +# client 1 +openssl genrsa -out client-key.pem 2048 +openssl req -new -key client-key.pem -out csr.pem -subj "/CN=my-client" +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 1000 + +# client 2 +openssl genrsa -out client-key-2.pem 2048 +openssl req -new -key client-key-2.pem -out csr.pem -subj "/CN=my-client-2" +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert-2.pem -days 1000 + +# create server cert with IP and DNS SANs +cat <req.cnf +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name + +[req_distinguished_name] + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = opa.example.com +IP.1 = 127.0.0.1 +EOF +openssl genrsa -out server-key.pem 2048 +openssl req -new -key server-key.pem -out csr.pem -subj "/CN=my-server" -config req.cnf +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 1000 -extensions v3_req -extfile req.cnf +``` + +We also create a simple authorization policy file, called `check.rego`: +```ruby +package system.authz + +# client_cns may defined in policy or pushed into OPA as data. +client_cns = { + "my-client": true +} + +default allow = false + +allow { # Allow request if + split(input.identity, "=", ["CN", cn]) # the cert subject is a CN, and + client_cns[cn] # the name is a known client. +} +``` + +Now, we're ready to starting the server with `-authentication=tls` and the +certificate-related parameters: +```console +$ opa run -s \ + --tls-cert-file server-cert.pem \ + --tls-private-key-file server-key.pem \ + --tls-ca-cert-file ca.pem \ + --authentication=tls \ + --authorization=basic \ + -a https://127.0.0.1:8181 \ + check.rego +INFO[2019-01-14T10:24:52+01:00] First line of log stream. addrs="[https://127.0.0.1:8181]" insecure_addr= +``` + +We can use `curl` to validate our TLS-based authentication setup: + +First, we use the client certificate that was signed by the CA, and has a subject +matching our authorization policy: + +```console +$ curl --key client-key.pem \ + --cert client-cert.pem \ + --cacert ca.pem \ + --resolve opa.example.com:8181:127.0.0.1 \ + https://opa.example.com:8181/v1/data +{"result":{}} +``` + +Note that we're passing the CA cert to curl -- this is done to have curl accept +the server's certificate, which has been signed by our CA cert. + +Since we've setup an IP SAN, we may also `curl https://127.0.0.1:8181/v1/data` +directly. (To keep our examples focused, we'll do that from here on.) + +Using a valid certificate whose subject will be declined by our authorization +policy: + +```console +$ curl --key client-key-2.pem \ + --cert client-cert-2.pem \ + --cacert ca.pem \ + https://127.0.0.1:8181/v1/data +{ + "code": "unauthorized", + "message": "request rejected by administrative policy" +} +``` + +Finally, we'll attempt to query without a client certificate: +```console +$ curl --cacert ca.pem https://127.0.0.1:8181/v1/data +curl: (35) error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate +``` + +As you can see, TLS-based authentication disallows these request completely. diff --git a/runtime/runtime.go b/runtime/runtime.go index 35f101237a..db88fd92f2 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -9,6 +9,7 @@ import ( "context" "crypto/rand" "crypto/tls" + "crypto/x509" "fmt" "io" "io/ioutil" @@ -71,6 +72,9 @@ type Params struct { // is nil, the server will NOT use TLS. Certificate *tls.Certificate + // CertPool holds the CA certs trusted by the OPA server. + CertPool *x509.CertPool + // HistoryPath is the filename to store the interactive shell user // input history. HistoryPath string @@ -235,6 +239,7 @@ func (rt *Runtime) StartServer(ctx context.Context) { WithAddresses(*rt.Params.Addrs). WithInsecureAddress(rt.Params.InsecureAddr). WithCertificate(rt.Params.Certificate). + WithCertPool(rt.Params.CertPool). WithAuthentication(rt.Params.Authentication). WithAuthorization(rt.Params.Authorization). WithDiagnosticsBuffer(rt.Params.DiagnosticsBuffer). diff --git a/server/identifier/identifier_test.go b/server/identifier/identifier_test.go index 1c5b8bbed5..1f1dcfad58 100644 --- a/server/identifier/identifier_test.go +++ b/server/identifier/identifier_test.go @@ -2,11 +2,13 @@ // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. -package identifier +package identifier_test import ( "net/http" "testing" + + "github.com/open-policy-agent/opa/server/identifier" ) type mockHandler struct { @@ -15,13 +17,13 @@ type mockHandler struct { } func (h *mockHandler) ServeHTTP(_ http.ResponseWriter, r *http.Request) { - h.identity, h.defined = Identity(r) + h.identity, h.defined = identifier.Identity(r) } func TestTokenBased(t *testing.T) { mock := &mockHandler{} - handler := NewTokenBased(mock) + handler := identifier.NewTokenBased(mock) req, err := http.NewRequest(http.MethodGet, "/foo/bar/baz", nil) if err != nil { diff --git a/server/identifier/testdata/.gitignore b/server/identifier/testdata/.gitignore new file mode 100644 index 0000000000..e8cd99200d --- /dev/null +++ b/server/identifier/testdata/.gitignore @@ -0,0 +1,5 @@ +*.srl +*.cnf +csr.pem +ca-key.pem +ca.pem diff --git a/server/identifier/testdata/cn-cert.pem b/server/identifier/testdata/cn-cert.pem new file mode 100644 index 0000000000..ec1514c63c --- /dev/null +++ b/server/identifier/testdata/cn-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5DCCAcygAwIBAgIJAJEUtRCBCdAGMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV +BAMMBW15LWNhMB4XDTE5MDExMTA5MjM1NFoXDTIxMTAwNzA5MjM1NFowFDESMBAG +A1UEAwwJbXktY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +2niMJJAlwthZjF2hT8IoaQ1FwZ1DyDPh0HBWRNQxAW0yNZVaeLUEwt9U5FkZiusr +bKDI0jD0MOVWQw1s8oj3ys7Zxei9B8c0KBhnXDbkKdFKs5uzgUMnJyyAwjRi8T3P +tyQtKK6phTBwseBEAXleoLYTUWAE9Q6W1wf9PHiY6Rak1HEUOU2AoV5gmZd8odKo +gn+5bmgm6so6BXty7DQod4J2tnq8/TC/1HM7TN+N0eaLtyjhmbU9TA/C7yeil4Fm +i3Sk+P/5CprLnPEGN3fXs5RAMk7PxHR+NhEIWMeNKzAUDMQZ662cWAJRe5L6l8aB +O4zN7vzcJLyiHfZMJEqzSQIDAQABoz0wOzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF +4DAhBgNVHREEGjAYghZjbGllbnQub3BhLmV4YW1wbGUuY29tMA0GCSqGSIb3DQEB +CwUAA4IBAQAYtVMyy799xyAbMzn0EuoFmrNaiuCWkNC4NV6I7CotEnBJqeCIR7LU +VxHOGPLeXlLki1rDx1elTNY3HuSSEyCTSAQc9thhVoBlnndHnTF+sTEiFBAZxrjw ++j7Kdh9AEAMOTAl/CnU8mziIDaoGLDEckpi32QzgGht4yIetNR85R6Y1J4RhuEI9 +cNMu5hMbEhL9L6wcIWEn9w9Y6bYqBHPWrprUG9AjTteP2CRadqYiHmbo8FG6Sor/ +jGIChe39L/fL6mG8bT4ageZdTWlA6fr+jcbnYFG+zhT9KhmUVlo7G7OMvgq81lsF +nCDsGTDE/U424T0s8uWw8P/WmTXJihr/ +-----END CERTIFICATE----- diff --git a/server/identifier/testdata/gencerts.sh b/server/identifier/testdata/gencerts.sh new file mode 100755 index 0000000000..f2a0b5d0dd --- /dev/null +++ b/server/identifier/testdata/gencerts.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# taken from +# https://github.com/dexidp/dex/blob/2d1ac74ec0ca12ae4d36072525d976c1a596820a/examples/k8s/gencert.sh#L22 + +cat <req.cnf +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name + +[req_distinguished_name] + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = client.opa.example.com +EOF + +openssl genrsa -out ca-key.pem 2048 +openssl req -x509 -new -nodes -key ca-key.pem -days 1000 -out ca.pem -subj "/CN=my-ca" + +openssl genrsa -out key.pem 2048 +openssl req -new -key key.pem -out csr.pem -subj "/CN=my-client" -config req.cnf +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cn-cert.pem -days 1000 -extensions v3_req -extfile req.cnf + +openssl req -new -key key.pem -out csr.pem -subj "/O=Torchwood/OU=opa-client-01" -config req.cnf +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out ou-cert.pem -days 1000 -extensions v3_req -extfile req.cnf diff --git a/server/identifier/testdata/key.pem b/server/identifier/testdata/key.pem new file mode 100644 index 0000000000..2e127e5572 --- /dev/null +++ b/server/identifier/testdata/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA2niMJJAlwthZjF2hT8IoaQ1FwZ1DyDPh0HBWRNQxAW0yNZVa +eLUEwt9U5FkZiusrbKDI0jD0MOVWQw1s8oj3ys7Zxei9B8c0KBhnXDbkKdFKs5uz +gUMnJyyAwjRi8T3PtyQtKK6phTBwseBEAXleoLYTUWAE9Q6W1wf9PHiY6Rak1HEU +OU2AoV5gmZd8odKogn+5bmgm6so6BXty7DQod4J2tnq8/TC/1HM7TN+N0eaLtyjh +mbU9TA/C7yeil4Fmi3Sk+P/5CprLnPEGN3fXs5RAMk7PxHR+NhEIWMeNKzAUDMQZ +662cWAJRe5L6l8aBO4zN7vzcJLyiHfZMJEqzSQIDAQABAoIBAQDBQl4GghVFVYlx +p+no2kJRG9KXQX0SfwLFFnraDDMFpgkCaYpMuSTrFhDMpxz3TK1vPJQpi/CXyGgU +jK3RpuQ8Xds7PXTqiodS6LOWWWBgtam1VIjoUfUyrCWCpkDYUuuKgNAJ6ug+z+kB +EPhXrXvOAwL3u07nUO6SbZjQg4YQubpT7znOzAaqE+33sCDsP/tdvliYdm18udvw +0TCTGUwEPGVfdYsSO8iHDikmaIFAjepd+w+PytsdCpzb7YjThHJu0SzvA3DozBWO +V0OpEX2tst48/HVYv7UrLeJXdizaa37o1v8Bxsai0p9WW9PY2376EglxPTf7EhTp +ANjrEFTZAoGBAPg3D2VBgETawknZHYOH+Zh0gTEEEOZkC+cJmgAVvVdjfIJXvJ8u +Rfrb13hCR2VS6xoymN6w8FTS1UTVBBV6IXssrFinWwuNQaoa3qrkmlNVl2nRQxvq +3OmqyckWXNqwBWL+Hhn0sfhD+b55C9evrcW2tf/6qj9w6r1qPle4D0NTAoGBAOFS +qy5ECUyZddJTpRhEDMOneFqMsymD28G6fuitACaxeqOAEaAMTSZDuf3JS6JkpnQS +EqrR1PEzFu2rKzRw/rX4GYplH/7gvimGd1hvLtftDCSMOFXwVmdgKHZjiqg74Nzl +QWH7kUfjNQgTldTFWzDLfuyIStXAv76f24UOsRdzAoGAaXyc4l9v79M4dsH6tQd4 +n74DmZ0swX0LQejmtdqHWThClfJLiyrTOsVrUQR56ynOGJggN6Piv2nKkTImRipd +SEe4BwU4wDQMEArTTrVQkNHzQ1lXt+mccQHQN9F1LMtZvrRYfpdreyMIZFZ1Hfjf +VQNNXbhd2hBW8qDQVd83PVkCgYAh9Gc/bZlJJccPjvNOGNMjmNUWMCW/l9NB+myt +e4SOUCh/Awmk6LWnkoUwrWjsa+Z5j0+o1j4UqvJFlonIOU7o9R5EMMEFk7CUaWMK +vJZ+i4ZM66SBrtoWcfMnBBEdEQjtwM59iX93KdIQCYOGsMbxL3lNA6zjUUyT2Vsn +TfN56QKBgAEGdgdbiidvZiifEKtrE7/dEOJvGa/d8zTAfyURQwqOSSN1MhECLyo4 +NBHjcH2AOW+lmwwCv5MyCX9VX8hUIVUllODcnU4ssMmliZmi6i6bqW+/zJ+w3p+F +RM3yPNTMHhM6IBgmXam7ZApN4NLdU4O5oZsLPS8tTQIWObP/M/nd +-----END RSA PRIVATE KEY----- diff --git a/server/identifier/testdata/ou-cert.pem b/server/identifier/testdata/ou-cert.pem new file mode 100644 index 0000000000..6cbad9327b --- /dev/null +++ b/server/identifier/testdata/ou-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC/DCCAeSgAwIBAgIJAJEUtRCBCdAHMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV +BAMMBW15LWNhMB4XDTE5MDExMTA5MjM1NFoXDTIxMTAwNzA5MjM1NFowLDESMBAG +A1UECgwJVG9yY2h3b29kMRYwFAYDVQQLDA1vcGEtY2xpZW50LTAxMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2niMJJAlwthZjF2hT8IoaQ1FwZ1DyDPh +0HBWRNQxAW0yNZVaeLUEwt9U5FkZiusrbKDI0jD0MOVWQw1s8oj3ys7Zxei9B8c0 +KBhnXDbkKdFKs5uzgUMnJyyAwjRi8T3PtyQtKK6phTBwseBEAXleoLYTUWAE9Q6W +1wf9PHiY6Rak1HEUOU2AoV5gmZd8odKogn+5bmgm6so6BXty7DQod4J2tnq8/TC/ +1HM7TN+N0eaLtyjhmbU9TA/C7yeil4Fmi3Sk+P/5CprLnPEGN3fXs5RAMk7PxHR+ +NhEIWMeNKzAUDMQZ662cWAJRe5L6l8aBO4zN7vzcJLyiHfZMJEqzSQIDAQABoz0w +OzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAhBgNVHREEGjAYghZjbGllbnQub3Bh +LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQA4rCzJLbAJ7hBdsoAn7vmn +RW7ut2JkUxrzxj7AaUd8p3wUddLzN8xkdAhNhFVhBS3lsVFyV+00uowvRqHiHCMz +r5KbBR8ZHTyaITDI3nybzDUctkjaYUmZ4F8P70Byi8xWsbagQ126wucCE/nZUDoR +jI87gn+TYL5w3mx5x0Sc/H69hwuTCNA5Q9PN8zANi5WupxHZpHWQdmkDzlbRSKRw +z75fYFYL1sxpDnPNZ6/7bNh+/XgvORYU3l9QwIL/y+yrZb0U2u9nvacNnnE2R6Z3 +XYJBh0I/RC5G58e9pJJc23awVjqh3mFlZ0QZN8hKmoVg3WqoKJ3bJ9MmuS2J1K5k +-----END CERTIFICATE----- diff --git a/server/identifier/tls.go b/server/identifier/tls.go new file mode 100644 index 0000000000..c27b6792f2 --- /dev/null +++ b/server/identifier/tls.go @@ -0,0 +1,31 @@ +// Copyright 2019 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package identifier + +import ( + "net/http" +) + +// TLSBased extracts the CN of the client's TLS ceritificate +type TLSBased struct { + inner http.Handler +} + +// NewTLSBased returns a new TLSBased object. +func NewTLSBased(inner http.Handler) *TLSBased { + return &TLSBased{ + inner: inner, + } +} + +func (h *TLSBased) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if tls := r.TLS; tls != nil { + if certs := tls.PeerCertificates; certs != nil && len(certs) > 0 { + r = SetIdentity(r, certs[0].Subject.ToRDNSequence().String()) + } + } + + h.inner.ServeHTTP(w, r) +} diff --git a/server/identifier/tls_test.go b/server/identifier/tls_test.go new file mode 100644 index 0000000000..372d031ab3 --- /dev/null +++ b/server/identifier/tls_test.go @@ -0,0 +1,83 @@ +// Copyright 2019 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package identifier_test + +import ( + "crypto/tls" + "net/http" + "net/http/httptest" + "testing" + + "github.com/open-policy-agent/opa/server/identifier" +) + +// Note: In these tests, we don't worry about the server actually verifying the +// client's certs; that's done in a different place. We only request it, and +// check what the identifier does with it. + +func TestTLSBased(t *testing.T) { + mock := &mockHandler{} + handler := identifier.NewTLSBased(mock) + + tests := []struct { + desc string + cert string + key string + expected string + defined bool + }{ + { + desc: "no cert", + }, + { + desc: "cert with CN=", + cert: "testdata/cn-cert.pem", + key: "testdata/key.pem", + expected: "CN=my-client", + defined: true, + }, + { + desc: "cert with long DN", + cert: "testdata/ou-cert.pem", + key: "testdata/key.pem", + expected: "OU=opa-client-01,O=Torchwood", + defined: true, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + // Note: some re-use happens if this server is outside of the tests loop, + // causing weird overlaps. Let's keep setting up a fresh one in each + // iteration to be safe. + s := httptest.NewUnstartedServer(handler) + s.TLS = &tls.Config{ClientAuth: tls.RequestClientCert} + s.StartTLS() + defer s.Close() + c := s.Client() // trusts the httptest server's TLS cert + + if tc.cert != "" && tc.key != "" { + cert, err := tls.LoadX509KeyPair(tc.cert, tc.key) + if err != nil { + t.Fatalf("read test cert/key (%s/%s): %s", tc.cert, tc.key, err) + } + c.Transport.(*http.Transport).TLSClientConfig.Certificates = []tls.Certificate{cert} + } + + _, err := c.Get(s.URL) + if err != nil { + t.Fatalf("unexpected error in GET %s: %s", s.URL, err) + } + if mock.defined != tc.defined { + t.Fatalf("Expected defined to be %v but got: %v", tc.defined, mock.defined) + } + + if mock.identity != tc.expected { + t.Fatalf("Expected identity to be %s but got: %s", tc.expected, mock.identity) + } + }) + } + +} diff --git a/server/server.go b/server/server.go index fbc86ea6b3..adbc1a4eb3 100644 --- a/server/server.go +++ b/server/server.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "html/template" @@ -48,8 +49,9 @@ type AuthenticationScheme int // Set of supported authentication schemes. const ( - AuthenticationOff AuthenticationScheme = iota - AuthenticationToken = iota + AuthenticationOff AuthenticationScheme = iota + AuthenticationToken + AuthenticationTLS ) // AuthorizationScheme enumerates the supported authorization schemes. The authorization @@ -58,8 +60,8 @@ type AuthorizationScheme int // Set of supported authorization schemes. const ( - AuthorizationOff AuthorizationScheme = iota - AuthorizationBasic = iota + AuthorizationOff AuthorizationScheme = iota + AuthorizationBasic ) // Set of handlers for use in the "handler" dimension of the duration metric. @@ -87,6 +89,7 @@ type Server struct { authentication AuthenticationScheme authorization AuthorizationScheme cert *tls.Certificate + certPool *x509.CertPool mtx sync.RWMutex partials map[string]rego.PartialResult store storage.Store @@ -129,6 +132,8 @@ func (s *Server) Init(ctx context.Context) (*Server, error) { switch s.authentication { case AuthenticationToken: s.Handler = identifier.NewTokenBased(s.Handler) + case AuthenticationTLS: + s.Handler = identifier.NewTLSBased(s.Handler) } txn, err := s.store.NewTransaction(ctx, storage.WriteParams) @@ -188,6 +193,12 @@ func (s *Server) WithCertificate(cert *tls.Certificate) *Server { return s } +// WithCertPool sets the server-side cert pool that the server will use. +func (s *Server) WithCertPool(pool *x509.CertPool) *Server { + s.certPool = pool + return s +} + // WithStore sets the storage used by the server. func (s *Server) WithStore(store storage.Store) *Server { s.store = store @@ -284,9 +295,7 @@ func (s *Server) getListenerForHTTPServer(u *url.URL) (Loop, error) { Handler: s.Handler, } - httpLoop := func() error { return httpServer.ListenAndServe() } - - return httpLoop, nil + return httpServer.ListenAndServe, nil } func (s *Server) getListenerForHTTPSServer(u *url.URL) (Loop, error) { @@ -300,8 +309,12 @@ func (s *Server) getListenerForHTTPSServer(u *url.URL) (Loop, error) { Handler: s.Handler, TLSConfig: &tls.Config{ Certificates: []tls.Certificate{*s.cert}, + ClientCAs: s.certPool, }, } + if s.authentication == AuthenticationTLS { + httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert + } httpsLoop := func() error { return httpsServer.ListenAndServeTLS("", "") } diff --git a/server/server_test.go b/server/server_test.go index cb4fe75a63..2ecc76c240 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -8,12 +8,16 @@ import ( "bufio" "bytes" "context" + "crypto/tls" + "crypto/x509" "encoding/json" "fmt" + "io/ioutil" "net" "net/http" "net/http/httptest" "net/http/httputil" + "net/url" "reflect" "regexp" "strings" @@ -2885,3 +2889,130 @@ func (m *mockResponseWriterConn) consumeQueryResultStream() ([]queryResultStream } return result, nil } + +func TestAuthenticationTLS(t *testing.T) { + ctx := context.Background() + store := inmem.New() + m, err := plugins.New([]byte{}, "test", store) + if err != nil { + t.Fatal(err) + } + + if err := m.Start(ctx); err != nil { + t.Fatal(err) + } + + txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams) + + authzPolicy := `package system.authz +import input.identity +default allow = false +allow { + identity = "CN=my-client" +}` + + if err := store.UpsertPolicy(ctx, txn, "test", []byte(authzPolicy)); err != nil { + t.Fatal(err) + } + + if err := store.Commit(ctx, txn); err != nil { + t.Fatal(err) + } + + caCertPEM, err := ioutil.ReadFile("testdata/ca.pem") + if err != nil { + t.Fatal(err) + } + pool := x509.NewCertPool() + if ok := pool.AppendCertsFromPEM(caCertPEM); !ok { + t.Fatal("failed to parse CA cert") + } + cert, err := tls.LoadX509KeyPair("testdata/server-cert.pem", "testdata/server-key.pem") + if err != nil { + t.Fatal(err) + } + + server, err := New(). + WithAddresses([]string{"https://127.0.0.1:8182"}). + WithStore(store). + WithManager(m). + WithCertificate(&cert). + WithCertPool(pool). + WithAuthentication(AuthenticationTLS). + WithAuthorization(AuthorizationBasic). + Init(ctx) + if err != nil { + t.Fatal(err) + } + + // Replicating some of what happens in the server's HTTPS listener + s := httptest.NewUnstartedServer(server.Handler) + s.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: pool, + } + s.StartTLS() + defer s.Close() + endpoint := s.URL + "/v1/data/foo" + + t.Run("happy path", func(t *testing.T) { + clientCert, err := tls.LoadX509KeyPair("testdata/client-cert.pem", "testdata/client-key.pem") + if err != nil { + t.Fatalf("read test client cert/key: %v", err) + } + c := http.DefaultClient + tr := http.DefaultTransport.(*http.Transport) + tr.TLSClientConfig = &tls.Config{ + Certificates: []tls.Certificate{clientCert}, + RootCAs: pool, + } + c.Transport = tr + + resp, err := c.Get(endpoint) + if err != nil { + t.Fatalf("GET: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status 200, got %s", resp.Status) + } + }) + + t.Run("authn successful, authz failed", func(t *testing.T) { + clientCert, err := tls.LoadX509KeyPair("testdata/client-cert-2.pem", "testdata/client-key-2.pem") + if err != nil { + t.Fatalf("read test client cert/key: %v", err) + } + c := http.DefaultClient + tr := http.DefaultTransport.(*http.Transport) + tr.TLSClientConfig = &tls.Config{ + Certificates: []tls.Certificate{clientCert}, + RootCAs: pool, + } + c.Transport = tr + + resp, err := c.Get(endpoint) + if err != nil { + t.Fatalf("GET: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusUnauthorized { + t.Errorf("expected status 401, got %s", resp.Status) + } + }) + + t.Run("client trusts server, but doesn't provide client cert", func(t *testing.T) { + c := http.DefaultClient + tr := http.DefaultTransport.(*http.Transport) + tr.TLSClientConfig = &tls.Config{ + RootCAs: pool, + } + c.Transport = tr + + _, err := c.Get(endpoint) + if _, ok := err.(*url.Error); !ok { + t.Errorf("expected *url.Error, got %T: %v", err, err) + } + }) +} diff --git a/server/testdata/.gitignore b/server/testdata/.gitignore new file mode 100644 index 0000000000..e7fe15b29b --- /dev/null +++ b/server/testdata/.gitignore @@ -0,0 +1,4 @@ +*.srl +*.cnf +csr.pem +ca-key.pem diff --git a/server/testdata/ca.pem b/server/testdata/ca.pem new file mode 100644 index 0000000000..2a0ebcbdac --- /dev/null +++ b/server/testdata/ca.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC8zCCAdugAwIBAgIJAMHu6iWKhLk/MA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV +BAMMBW15LWNhMB4XDTE5MDExMTExMjI0MFoXDTIxMTAwNzExMjI0MFowEDEOMAwG +A1UEAwwFbXktY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7RmRo +rdbz/cObXySp8ul4430gWuQxo0GKplxf4TECSpNxY3HUZ3bdexi/d06ZQSdxPbKg +DXwvuzNz2NrBk++CgSYU8clwiFpjB0WIzQZDe1rMRSYfD75n6AcqXj+0uz/M+bEy +HQUGgRqLdmWwLB7APvgqHXbjpPVHWi1iQDWsflmXK5FzHwIOc20H0CgszfpTY3q4 +uDbeXunlwsdpBsT0rV6F4sipO/qpbPuXQ54pXE7OcFyvEJKE++NXOEGkxML1hLws +SI3kVYgkHVp9uRNm7+wijTDY8xdNqGEiP9nEg9uFbR0hvhIWWqmHY4vOLiCKZTYC +V8baC7Y3g6Osi+2hAgMBAAGjUDBOMB0GA1UdDgQWBBRQEkQ+CO07gADyd2girlSx +VTS27jAfBgNVHSMEGDAWgBRQEkQ+CO07gADyd2girlSxVTS27jAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCb532T6FXc83aahdyhd1PgqqrQQqb80j5H +ep5nkLPUwf4qOfMqVG75n7WmlUKe7eVU0R4vp1frUGazh6/GV9ZoaEp2/Gh/JIUc +KEz2a6zvu8+13vRTfp5B3P06ek0FaqAljer6oSfV6/tMTQ9ArLezVdnvfX4+gf7h +2443SDo6g0LeFXc4CeIrff1b3sOqpvn2F+b87qQFy0EDRbF2i8p2ZmheompWPlmw +d0LXd8aXHWOzfpUkI5mLMiN7Ft1j8k701SeuOsawIDmTy3looN+tq1kl7cJswWrs +uwK+XpX0qg815BB5jcEwOvpk+bMKzBhkIf8VGFBL88A6anwuX6k7 +-----END CERTIFICATE----- diff --git a/server/testdata/client-cert-2.pem b/server/testdata/client-cert-2.pem new file mode 100644 index 0000000000..91125e899f --- /dev/null +++ b/server/testdata/client-cert-2.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICojCCAYoCCQDOA0JzmDQJATANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQDDAVt +eS1jYTAeFw0xOTAxMTExMTIyNDBaFw0yMTEwMDcxMTIyNDBaMBYxFDASBgNVBAMM +C215LWNsaWVudC0yMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv4WZ +biCVj4yfITYSH5Bocph7xy+gvsCVYUWZU8Nulgo3OLx4q1qRKMjGqDvhMK6BvPoE +bUoMRHn8Jk9ABIMdLKBwpby9oovZOwSYh8NLrkmaUlzTDQlzDEdKYf428b0n6HOZ +045GU5Tvm06ER2fa/qqlsCfP6adr9+u0KnL9KIZkh1PM69vUF+JteXAyDnYrciIN +RcEq9Fb6gcMG3mBr0jAKVDA7KtFf/diPfEtKxtASYq98g7AY5JgjI8lovGkXQMJc +kCQxWQ+uaX1tr7tRpNLMM7n6MTV3HvKAp8zCjijOcog/7h0atd3n3u2tCbchEPnT ++Jt/ikFa3VFs3oJzFwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAtTXalo8+0Cg7d +9gzMODAgCSynumzTmOyH1IYL8UmNgHImG26EpDmda2EKsIDTcgQV1dKDKPCTJwoB +akBwb9+EwqEMd7h7bGKhPizg7tguIz4h08UYZkoVcyEiU8Azc6X8dLcYjKNTl5XH +wIX8wEt+yU973bWKqFuMqNT7Ex70efDX2DVa5ZOrIKltyx5K0cv0qQZ7HZULIMyh +iWGC3LiB48gMRx7CkyCiBvCAg/Ux8/dNi87p72pzerpyRJt0VbU94SW9tsVLWeOj +KtGdnzH2J9Uop4+B7Aa/TBqsXue0JFJRkjf5YbVkY1b6SFPQA7t2mbf42DILC7Ff +rmNWH95k +-----END CERTIFICATE----- diff --git a/server/testdata/client-cert.pem b/server/testdata/client-cert.pem new file mode 100644 index 0000000000..2d28e26e22 --- /dev/null +++ b/server/testdata/client-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICoDCCAYgCCQDOA0JzmDQJADANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQDDAVt +eS1jYTAeFw0xOTAxMTExMTIyNDBaFw0yMTEwMDcxMTIyNDBaMBQxEjAQBgNVBAMM +CW15LWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALUVYoeA +pg0UEL+ixFwRU16ukrE1CMTSCGAVYaadxMjaFd8QNXzP+lK3eyttEFPoVRZJ6z12 +JxUSRYhiIai2kII2ySrkqWvD9G6To/YJdgw9HdDjkibZxG536jQUAx3NHZib7qL2 +AGd9Sm1XJd9jgORlQ7e26V2y1KOE4v6GUGBzeWPhgYPsCKI16wdr40d9co/s6Ycb +Ft90r3KdBLW60bCLBgJKYBo/1oVDiVJW8mY+eFwqqdm2G/Fftcyf7UySFCVE8WPg +Xtb7M3nSlcD8Cc86Z93OfLd+E87THLqDg8vsp++yj3voHoDr4eyiItHVqklbM4Kb +38JMTsR2UAif5YMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAuTKYwIfvnWgL0Lw+ +oT054kwhcODyzQ0kHXNhxywPCEWRF8n9uDQOyy4nG7zHtIf2LG5iB/uwhS4QLpZ+ +k09rWI3+pVKyPOHTwBiUHmfKz5Neh+VGbY4hW4TvQT/XQgTTAcvc3dMAP/0KN9wp +YjE9COBVNZT1pxDooczQA8KwhIIQY8v8TnFyh0tOGX9o4F6VtLoEPrvfphh4ozNh +2cP6HD+BvygsK53jl7xN94RQeAq4PCw+GZR47JN/QCe9tkHVLe2McDZoMsxCtoBX +gK4rc4gLmUaSdsrSnU9isJM5nUz+6m705dzCjJZhM70LYbWiqbChs+YqCHsJChOj +PmigKg== +-----END CERTIFICATE----- diff --git a/server/testdata/client-key-2.pem b/server/testdata/client-key-2.pem new file mode 100644 index 0000000000..339e47766d --- /dev/null +++ b/server/testdata/client-key-2.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAv4WZbiCVj4yfITYSH5Bocph7xy+gvsCVYUWZU8Nulgo3OLx4 +q1qRKMjGqDvhMK6BvPoEbUoMRHn8Jk9ABIMdLKBwpby9oovZOwSYh8NLrkmaUlzT +DQlzDEdKYf428b0n6HOZ045GU5Tvm06ER2fa/qqlsCfP6adr9+u0KnL9KIZkh1PM +69vUF+JteXAyDnYrciINRcEq9Fb6gcMG3mBr0jAKVDA7KtFf/diPfEtKxtASYq98 +g7AY5JgjI8lovGkXQMJckCQxWQ+uaX1tr7tRpNLMM7n6MTV3HvKAp8zCjijOcog/ +7h0atd3n3u2tCbchEPnT+Jt/ikFa3VFs3oJzFwIDAQABAoIBAFlZy+Xr/7qT4V2O +K1Bcf84Ow5h+6OB1WnuiC9FfWMVZCMBIdYeD3jsHyF6OaRXcJBz1C4GtTSHZQ0Dg +Mj6oqMC8LTG1Z0dhNTxqfXrBKxqprfyE39WyNeFhlRs4jYukMu0omXhwZPDziAGN +1Pq3Qh0Toe8X89DPUfi9FzXCpcb0dIMJQLUMt2PLXTlcx4PD8wXR9m5hJsuevgc5 +WEUCqS97F0d/4ilTVlUu9pbRF4KpPvbXabDi7xcyk5qQDRv7/4kVQrK0IbXRjFTo +RRooip8o4lVy0pZN03mTEAZo4OZvJ/rOqfFmpZyBeJxIIgCrhtvTO8E1OmBkRrrl ++20W/IkCgYEA823mJallv/CzC1mUBFA3ESughsvfhT6vkZ6q+MjYlRf2hKPWtfY+ +dA/wwkNIrjVsW/B+CPply/bYH4CY9VFYMstQI4VcsVDZF33Hc3yFWGQwgMwnnAh+ +OGsQrJ2sUcuHsoXrssFk9MdByc9kdLt7lBN5qvPsGaEIMbY4lEca3L0CgYEAyWl9 +1PTEF+IRufusLZrImFSl2DnMkkUNImvi80NMFqUFtof5c/sMWPs7ilvixFMFrANE +Nd4isFA52Wl4PhDsRgkLZ/BSj7cAtX6dq74pTIotR6ApnV9+9MI2Q3QCuP2Bu4ck +1qeAlffLTkx78lOyFtRfVXMH+DixZNdhq6DrzmMCgYAEYCNU5fiYPKFbQN7yPObg +fiJCigOYh4nsWifElQeflnCt7av8VVLGD0tKkp8J0pgCBw6c5rNRuTwlRNB7CF1H +fHQST2FdJpDgZThikQhskFB24DSOD3EFXpdMIwFn7vqfcNzNt8AyzioWkI3Ds2MD +RF5ZfkzuIQDes+HMeIK5GQKBgQCqCxgjTKqph0F0BZitv/oPMZf634FpFEcsgm1O +98grbhNOsanXU2JvVVxFSaJAId+uA+v4lpwuwuy0MylDLUJtNoGctPbfQp+km0s+ +jgOMDTSBPiUkKlWjCryJYk5SJCT9T/G4EB6tflD0v/n88tBuGcqK990IocSS4Iwj +PG8ZBQKBgGwZfRkYKoVB4VjewHL1+JLPvWOcsCEc+3T6mkizxpPK7fewi2YChcCn +q8YrTP41ZUlcwALbxsSD2SQPjcjFKhgiFOOezV3qJ/oDC4+SC0/1jAQHWHC20kKZ +ZyWBL6FdR+yUY7Zafo16M49ioZh8hYrA6gYDC/Zm/sKXdIaTveQU +-----END RSA PRIVATE KEY----- diff --git a/server/testdata/client-key.pem b/server/testdata/client-key.pem new file mode 100644 index 0000000000..087ea74f53 --- /dev/null +++ b/server/testdata/client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAtRVih4CmDRQQv6LEXBFTXq6SsTUIxNIIYBVhpp3EyNoV3xA1 +fM/6Urd7K20QU+hVFknrPXYnFRJFiGIhqLaQgjbJKuSpa8P0bpOj9gl2DD0d0OOS +JtnEbnfqNBQDHc0dmJvuovYAZ31KbVcl32OA5GVDt7bpXbLUo4Ti/oZQYHN5Y+GB +g+wIojXrB2vjR31yj+zphxsW33Svcp0EtbrRsIsGAkpgGj/WhUOJUlbyZj54XCqp +2bYb8V+1zJ/tTJIUJUTxY+Be1vszedKVwPwJzzpn3c58t34TztMcuoODy+yn77KP +e+gegOvh7KIi0dWqSVszgpvfwkxOxHZQCJ/lgwIDAQABAoIBAE4QqwpZdrx6owHK +jLZYLcXKoqjMgTxAg6XQcemfaD9ibT1dwoFNM9BHE01UVk6mAVB7i3NSpWSbHOi/ +zVzrX38TWUQpkbRIpO6qrWubBo4eJuVvxgkDpoAtKAVNf05wp8qrpoQQY3o59EUQ +5d3ZCq7rzPa9Xvuq5aqc1jL9+lapl5/hxXKeDXID8yVqUlpnDsf2KzsxH4Fxu1o5 +pdUbLLKTDGfrKHAiUq90z2uerFg3ENOeTlOhZSwJ5FzFx8BN2jajxpXG9EnphdJj +VJg8Vas6bJzN729xEtzp4EIGHc+jtbLpCagi8OwZh0lf8ZsbFSMqLMIB3znsMYof +40yKwFECgYEA4Ql+LmMBuweHSRlvgbh++zg65tztxBPUCOXdh75J3Sr4Lssox5T2 +lqZWKG7sijNwxN6U2TlSONK5wsMFtU1f+UMUGHiyLmTac0Tnp5CdVUM3yn+BwM1f +Lww+uypBCWCEnFYSsl81gGiJeS80GA1MuNI+FtD92cZ2gfcIOCD5EksCgYEAzf+2 +DGwUpQGArHt8rXupx9Jx6UgbVxErH1VG0o8RU8qOovYBPFvVlB8xPL/OHUrUXE8C +s4hDZ8TDP7uTtUzqVlr7nv7wAx1rIoTqbqaESoTsdklISG3KUMtYDTBDo7Zx3UZU +R+mPqOVEbpEqM+B//R/he4Gdk8frxMonuNClNqkCgYAcnQVjRol4y3tDKy3Hc7sJ +nFu48Qx2awMB4qBpWyOvMICACqrzvZghDaNU3s3KwMGs9pQ8jpJK0Vh3UC7Qn7b9 +Ta8ncWlOhtpT38YIw1WN+s8EO3Q3HKXmqIrtZ3D/jTsvWa5k6cA2xJP+Sac5C6/e +rDTi183/O8HjwpJT+LVSwwKBgDncYV4L3aCbU3EfHl38JiwiSCymPEewYtRiN4pc +9Rj8BCRxxNcXVF5OhJ3zoglIQ23KI5AZ7hTvh4gXP93kxM2MsLBwGypE0RrZCqAZ +1X6451IyM0Nk0zbuZsUNkXIihMkKQBIS4G8oNph33crEfC61DbiLJ5+iv+5+ebnH +oSBBAoGAWCYAO14A/UBx3EwPac4Tw22DhgC7AM8TBiMKpMsXNJrhoMfB2Jlx4p6e ++B1EExck0fdp46FJaXFFEKmu0xAXZ8Xyi7N+IpDGhIVkHhBANdvoMmwdKKTPcajT +5V40n97gka96YC8TPrWQrpEgFhTCNaFgEfzy2cixKjfNtiBMZQk= +-----END RSA PRIVATE KEY----- diff --git a/server/testdata/gencerts.sh b/server/testdata/gencerts.sh new file mode 100755 index 0000000000..4cd50ad6fa --- /dev/null +++ b/server/testdata/gencerts.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# taken from +# https://github.com/dexidp/dex/blob/2d1ac74ec0ca12ae4d36072525d976c1a596820a/examples/k8s/gencert.sh#L22 + +cat <req.cnf +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name + +[req_distinguished_name] + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = opa.example.com +IP.1 = 127.0.0.1 +EOF + +openssl genrsa -out ca-key.pem 2048 +openssl req -x509 -new -nodes -key ca-key.pem -days 1000 -out ca.pem -subj "/CN=my-ca" + +openssl genrsa -out client-key.pem 2048 +openssl req -new -key client-key.pem -out csr.pem -subj "/CN=my-client" +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 1000 + +openssl genrsa -out client-key-2.pem 2048 +openssl req -new -key client-key-2.pem -out csr.pem -subj "/CN=my-client-2" +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert-2.pem -days 1000 + +openssl genrsa -out server-key.pem 2048 +openssl req -new -key server-key.pem -out csr.pem -subj "/CN=my-server" -config req.cnf +openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 1000 -extensions v3_req -extfile req.cnf diff --git a/server/testdata/server-cert.pem b/server/testdata/server-cert.pem new file mode 100644 index 0000000000..642248b37b --- /dev/null +++ b/server/testdata/server-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC4zCCAcugAwIBAgIJAM4DQnOYNAkCMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV +BAMMBW15LWNhMB4XDTE5MDExMTExMjI0MFoXDTIxMTAwNzExMjI0MFowFDESMBAG +A1UEAwwJbXktc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +wLsRfXAK6EfjLNl+JPoPa0dYOigjqj3MDyMRjoYoOox0PD5oYVHQZ9vgtllihTxd +q9aHnO1eW5dZungit3OiomC9dl9OTX1mVulO0U0T6wuksHC2z/SRnmKYYCO0yNqJ +VVniZ8c6J2ZwtNUQytSMFPvOGWSK9z2jcyhoUCsg88+7xXmuKFGTHWhKBlkCg0DJ +7FjN2PXJ3osQwjTSX8z2emyTvdJI/ZYBb1z64aysBLdV3QO+PIBYzhKlVuSBWNUQ +F+JR30iUyeN8bWjMCGftLJc58bZsQsIBWcLJN+os/DWt2dvv2/TFMiwxSYKOy6rA +bsOPb0vSRKjd8Th62VOlxQIDAQABozwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF +4DAgBgNVHREEGTAXgg9vcGEuZXhhbXBsZS5jb22HBH8AAAEwDQYJKoZIhvcNAQEL +BQADggEBAJq6pCjj6db/48oZoRUTdIfzpqk/cqWlvWIklVNX+2wWrZ4AGAro7G1z +ppNL9nIfBp6gO62iotFCrudlrtq1M2YaiXXBGwVPZcQjThhVp/gd+1VNUL+0PB3Z +8N5wgHch9mzMgHStfOLtwR6tFDGWXfcdIMwEHehZH5M0uQbJYEkBL5xh1DEuK0/c +kTiSdb4vFCrX7C67YMzfCuoq5xGr5ZNU0NugzAmf/fZhFFmDVSr6/GfS02Ippfw8 +pBt42gFcEDPpK2wFhqMfiIIIvOvgtkjvx9ctY5boRnh1sZcayDMq/QfPOrsj55Vv +Cq8Il7ESE2bJP1Bt9/rHLCKP6GKD6Jk= +-----END CERTIFICATE----- diff --git a/server/testdata/server-key.pem b/server/testdata/server-key.pem new file mode 100644 index 0000000000..df19df973b --- /dev/null +++ b/server/testdata/server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwLsRfXAK6EfjLNl+JPoPa0dYOigjqj3MDyMRjoYoOox0PD5o +YVHQZ9vgtllihTxdq9aHnO1eW5dZungit3OiomC9dl9OTX1mVulO0U0T6wuksHC2 +z/SRnmKYYCO0yNqJVVniZ8c6J2ZwtNUQytSMFPvOGWSK9z2jcyhoUCsg88+7xXmu +KFGTHWhKBlkCg0DJ7FjN2PXJ3osQwjTSX8z2emyTvdJI/ZYBb1z64aysBLdV3QO+ +PIBYzhKlVuSBWNUQF+JR30iUyeN8bWjMCGftLJc58bZsQsIBWcLJN+os/DWt2dvv +2/TFMiwxSYKOy6rAbsOPb0vSRKjd8Th62VOlxQIDAQABAoIBAHFybCQrPLBoCGhR +CfjYHRVn5J4vHHdMcv6CBQahH7L/FDiV/7kxgHxyKD8ONHj7BKRu3lrYOo+tcorZ +xo24CoaMbVntVdk1NHV6VgPL3CqiKuoVHvcgHktd/BWzTupgATD1fNjbo/anTTaU +qJLMNDzxz+qQjWBlrv3NTkHKcVYx7td4jkd8v3NurI9HmwABRKWTiZzKS/4mpIKS +m/A7u4oM7GQAKKbHDYz2sGDp4BdrWKm4plTU3RRPfhpUFc0sy/6BXwY9nIRFO/aa +UGub3ZgiW2GyXhmUzZIrs+SSOPNbHdf05225ajdh4tfyPcGetyWoa7rWI7au9dTn +192zN2ECgYEA5LurcFmFU8pYr3jthKa6mSCDuIzniAMvcjnv6ociyMPpEff74nD7 +h8ETAKkQYDwJn1wjys2m5i/c3Uji72m4kDioCRLQY0xoC7X8/lXiSXQ7aiDNqxZW +bDuZPl0ryYZmeiAn/vjOR9BnUFYuyyTs7z1K7w8vZe0JNnjF2kdZXT0CgYEA17Sz +sTBjSPBeTRAweaMHBOSY04tuS8gOUxC9RHeWdtt0JDfGe6Hp8K99D0exUbAkX0B2 +hDxVk3+lq22H/wk7zP6WXPLLlyzyyeL1ZVNb2Br4mVcSjMnlIL235HrCOjMprVFa +LBOzcMLtXSptDmvDdZmAUKr0HmBMlkOX8zZcAykCgYBSMVXzs1eDuoyP0YaYSkl3 +SQXMRWXVrD9abpNV1WWcezm/aTssLalVKP4pGJd33Vsf1r5N3ASDZuOY6N4TZgwa +VyGa0RXs+MHSo3zb8AS2nHvVMSB2wDoh/nCcxmI7sn4UmIWGy+VkTPEzHyUFfqld +dsr8iJphqAHNfyypuUXViQKBgBCsjT9jKCmZOxDl8XlVmtNMAGxJ2OrOuhy9rIPA +YscpS58JGLSx3W2XgylNN23DGeyrBP5P06WlNl88Bkk2o4LDI0hoFEjJVrM4chO1 +D+Jyo0jnLC3p5WZUhASLYLwy/EPDiB7kHvjWeJa9EtUMi31psjuKG3jFpOXwr6xD +RXWRAoGBAN7bGfYzHKhKiSI67PpeQ5S7KtK8/fi0wSuDoG1J6OkMBk35FJ6wPkPX +iQVpMY0bvdQgwo0hug/3NQCKDKAnPLO9vpHZHlP+fn8F6Vq4/pvhvFSc9qihs37P +A+kLIr1vKOL2MUIN/b3B4D6U27FwO7aJ5LYlAoPgNmMZTKSVAr42 +-----END RSA PRIVATE KEY-----