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 <srenatus@chef.io>
This commit is contained in:
Stephan Renatus
2019-01-11 10:26:36 +01:00
committed by Torin Sandall
parent 85b1931abf
commit 3286c39822
22 changed files with 735 additions and 14 deletions
+29 -4
View File
@@ -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
}
+144
View File
@@ -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=<path>`` 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 <<EOF >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.
+5
View File
@@ -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).
+5 -3
View File
@@ -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 {
+5
View File
@@ -0,0 +1,5 @@
*.srl
*.cnf
csr.pem
ca-key.pem
ca.pem
+18
View File
@@ -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-----
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# taken from
# https://github.com/dexidp/dex/blob/2d1ac74ec0ca12ae4d36072525d976c1a596820a/examples/k8s/gencert.sh#L22
cat <<EOF >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
+27
View File
@@ -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-----
+18
View File
@@ -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-----
+31
View File
@@ -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)
}
+83
View File
@@ -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=<name>",
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)
}
})
}
}
+20 -7
View File
@@ -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("", "") }
+131
View File
@@ -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)
}
})
}
+4
View File
@@ -0,0 +1,4 @@
*.srl
*.cnf
csr.pem
ca-key.pem
+18
View File
@@ -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-----
+17
View File
@@ -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-----
+17
View File
@@ -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-----
+27
View File
@@ -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-----
+27
View File
@@ -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-----
Vendored Executable
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# taken from
# https://github.com/dexidp/dex/blob/2d1ac74ec0ca12ae4d36072525d976c1a596820a/examples/k8s/gencert.sh#L22
cat <<EOF >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
+18
View File
@@ -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-----
+27
View File
@@ -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-----