709 lines
21 KiB
Go
709 lines
21 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package nomad
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
nomadapi "github.com/hashicorp/nomad/api"
|
|
"github.com/hashicorp/vault/helper/testhelpers"
|
|
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
type Config struct {
|
|
docker.ServiceURL
|
|
Token string
|
|
}
|
|
|
|
func (c *Config) APIConfig() *nomadapi.Config {
|
|
apiConfig := nomadapi.DefaultConfig()
|
|
apiConfig.Address = c.URL().String()
|
|
apiConfig.SecretID = c.Token
|
|
return apiConfig
|
|
}
|
|
|
|
func (c *Config) Client() (*nomadapi.Client, error) {
|
|
apiConfig := c.APIConfig()
|
|
|
|
return nomadapi.NewClient(apiConfig)
|
|
}
|
|
|
|
func prepareTestContainer(t *testing.T, bootstrap bool) (func(), *Config) {
|
|
if retAddress := os.Getenv("NOMAD_ADDR"); retAddress != "" {
|
|
s, err := docker.NewServiceURLParse(retAddress)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return func() {}, &Config{*s, os.Getenv("NOMAD_TOKEN")}
|
|
}
|
|
|
|
runner, err := docker.NewServiceRunner(docker.RunOptions{
|
|
ImageRepo: "multani/nomad",
|
|
ImageTag: "1.1.6",
|
|
ContainerName: "nomad",
|
|
Ports: []string{"4646/tcp"},
|
|
Cmd: []string{"agent", "-dev"},
|
|
Env: []string{`NOMAD_LOCAL_CONFIG=bind_addr = "0.0.0.0" acl { enabled = true }`},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Could not start docker Nomad: %s", err)
|
|
}
|
|
|
|
var nomadToken string
|
|
svc, err := runner.StartService(context.Background(), func(ctx context.Context, host string, port int) (docker.ServiceConfig, error) {
|
|
var err error
|
|
nomadapiConfig := nomadapi.DefaultConfig()
|
|
nomadapiConfig.Address = fmt.Sprintf("http://%s:%d/", host, port)
|
|
nomad, err := nomadapi.NewClient(nomadapiConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = nomad.Status().Leader()
|
|
if err != nil {
|
|
t.Logf("[DEBUG] Nomad is not ready yet: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
if bootstrap {
|
|
aclbootstrap, _, err := nomad.ACLTokens().Bootstrap(nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nomadToken = aclbootstrap.SecretID
|
|
t.Logf("[WARN] Generated Master token: %s", nomadToken)
|
|
}
|
|
|
|
nomadAuthConfig := nomadapi.DefaultConfig()
|
|
nomadAuthConfig.Address = nomad.Address()
|
|
|
|
if bootstrap {
|
|
nomadAuthConfig.SecretID = nomadToken
|
|
|
|
nomadAuth, err := nomadapi.NewClient(nomadAuthConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = preprePolicies(nomadAuth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
u, _ := docker.NewServiceURLParse(nomadapiConfig.Address)
|
|
return &Config{
|
|
ServiceURL: *u,
|
|
Token: nomadToken,
|
|
}, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Could not start docker Nomad: %s", err)
|
|
}
|
|
|
|
return svc.Cleanup, svc.Config.(*Config)
|
|
}
|
|
|
|
func preprePolicies(nomadClient *nomadapi.Client) error {
|
|
policy := &nomadapi.ACLPolicy{
|
|
Name: "test",
|
|
Description: "test",
|
|
Rules: `namespace "default" {
|
|
policy = "read"
|
|
}
|
|
`,
|
|
}
|
|
anonPolicy := &nomadapi.ACLPolicy{
|
|
Name: "anonymous",
|
|
Description: "Deny all access for anonymous requests",
|
|
Rules: `namespace "default" {
|
|
policy = "deny"
|
|
}
|
|
agent {
|
|
policy = "deny"
|
|
}
|
|
node {
|
|
policy = "deny"
|
|
}
|
|
`,
|
|
}
|
|
|
|
_, err := nomadClient.ACLPolicies().Upsert(policy, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = nomadClient.ACLPolicies().Upsert(anonPolicy, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func TestBackend_config_Bootstrap(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cleanup, svccfg := prepareTestContainer(t, false)
|
|
defer cleanup()
|
|
|
|
connData := map[string]interface{}{
|
|
"address": svccfg.URL().String(),
|
|
"token": "",
|
|
}
|
|
|
|
confReq := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/access",
|
|
Storage: config.StorageView,
|
|
Data: connData,
|
|
}
|
|
|
|
resp, err := b.HandleRequest(context.Background(), confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
|
|
confReq.Operation = logical.ReadOperation
|
|
resp, err = b.HandleRequest(context.Background(), confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"address": connData["address"].(string),
|
|
"max_token_name_length": 0,
|
|
"ca_cert": "",
|
|
"client_cert": "",
|
|
}
|
|
if !reflect.DeepEqual(expected, resp.Data) {
|
|
t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
|
|
}
|
|
|
|
nomadClient, err := svccfg.Client()
|
|
if err != nil {
|
|
t.Fatalf("failed to construct nomaad client, %v", err)
|
|
}
|
|
|
|
token, _, err := nomadClient.ACLTokens().Bootstrap(nil)
|
|
if err == nil {
|
|
t.Fatalf("expected acl system to be bootstrapped already, but was able to get the bootstrap token : %v", token)
|
|
}
|
|
// NOTE: fragile test, but it's the only way, AFAIK, to check that nomad is
|
|
// bootstrapped
|
|
if !strings.Contains(err.Error(), "bootstrap already done") {
|
|
t.Fatalf("expected acl system to be bootstrapped already: err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBackend_config_access(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cleanup, svccfg := prepareTestContainer(t, true)
|
|
defer cleanup()
|
|
|
|
connData := map[string]interface{}{
|
|
"address": svccfg.URL().String(),
|
|
"token": svccfg.Token,
|
|
}
|
|
|
|
confReq := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/access",
|
|
Storage: config.StorageView,
|
|
Data: connData,
|
|
}
|
|
|
|
resp, err := b.HandleRequest(context.Background(), confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
|
|
confReq.Operation = logical.ReadOperation
|
|
resp, err = b.HandleRequest(context.Background(), confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"address": connData["address"].(string),
|
|
"max_token_name_length": 0,
|
|
"ca_cert": "",
|
|
"client_cert": "",
|
|
}
|
|
if !reflect.DeepEqual(expected, resp.Data) {
|
|
t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
|
|
}
|
|
if resp.Data["token"] != nil {
|
|
t.Fatalf("token should not be set in the response")
|
|
}
|
|
}
|
|
|
|
func TestBackend_config_access_with_certs(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cleanup, svccfg := prepareTestContainer(t, true)
|
|
defer cleanup()
|
|
|
|
connData := map[string]interface{}{
|
|
"address": svccfg.URL().String(),
|
|
"token": svccfg.Token,
|
|
"ca_cert": caCert,
|
|
"client_cert": clientCert,
|
|
"client_key": clientKey,
|
|
}
|
|
|
|
confReq := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/access",
|
|
Storage: config.StorageView,
|
|
Data: connData,
|
|
}
|
|
|
|
resp, err := b.HandleRequest(context.Background(), confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
|
|
confReq.Operation = logical.ReadOperation
|
|
resp, err = b.HandleRequest(context.Background(), confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"address": connData["address"].(string),
|
|
"max_token_name_length": 0,
|
|
"ca_cert": caCert,
|
|
"client_cert": clientCert,
|
|
}
|
|
if !reflect.DeepEqual(expected, resp.Data) {
|
|
t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
|
|
}
|
|
if resp.Data["token"] != nil {
|
|
t.Fatalf("token should not be set in the response")
|
|
}
|
|
}
|
|
|
|
func TestBackend_renew_revoke(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cleanup, svccfg := prepareTestContainer(t, true)
|
|
defer cleanup()
|
|
|
|
connData := map[string]interface{}{
|
|
"address": svccfg.URL().String(),
|
|
"token": svccfg.Token,
|
|
}
|
|
|
|
req := &logical.Request{
|
|
Storage: config.StorageView,
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/access",
|
|
Data: connData,
|
|
}
|
|
resp, err := b.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req.Path = "role/test"
|
|
req.Data = map[string]interface{}{
|
|
"policies": []string{"policy"},
|
|
"lease": "6h",
|
|
}
|
|
resp, err = b.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req.Operation = logical.ReadOperation
|
|
req.Path = "creds/test"
|
|
resp, err = b.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("resp nil")
|
|
}
|
|
if resp.IsError() {
|
|
t.Fatalf("resp is error: %v", resp.Error())
|
|
}
|
|
|
|
generatedSecret := resp.Secret
|
|
generatedSecret.TTL = 6 * time.Hour
|
|
|
|
var d struct {
|
|
Token string `mapstructure:"secret_id"`
|
|
Accessor string `mapstructure:"accessor_id"`
|
|
}
|
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("[WARN] Generated token: %s with accessor %s", d.Token, d.Accessor)
|
|
|
|
// Build a client and verify that the credentials work
|
|
nomadapiConfig := nomadapi.DefaultConfig()
|
|
nomadapiConfig.Address = connData["address"].(string)
|
|
nomadapiConfig.SecretID = d.Token
|
|
client, err := nomadapi.NewClient(nomadapiConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Log("[WARN] Verifying that the generated token works...")
|
|
_, err = client.Agent().Members, nil
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req.Operation = logical.RenewOperation
|
|
req.Secret = generatedSecret
|
|
resp, err = b.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("got nil response from renew")
|
|
}
|
|
|
|
req.Operation = logical.RevokeOperation
|
|
resp, err = b.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Build a management client and verify that the token does not exist anymore
|
|
nomadmgmtConfig := nomadapi.DefaultConfig()
|
|
nomadmgmtConfig.Address = connData["address"].(string)
|
|
nomadmgmtConfig.SecretID = connData["token"].(string)
|
|
mgmtclient, err := nomadapi.NewClient(nomadmgmtConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
q := &nomadapi.QueryOptions{
|
|
Namespace: "default",
|
|
}
|
|
|
|
t.Log("[WARN] Verifying that the generated token does not exist...")
|
|
_, _, err = mgmtclient.ACLTokens().Info(d.Accessor, q)
|
|
if err == nil {
|
|
t.Fatal("err: expected error")
|
|
}
|
|
}
|
|
|
|
func TestBackend_CredsCreateEnvVar(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cleanup, svccfg := prepareTestContainer(t, true)
|
|
defer cleanup()
|
|
|
|
req := logical.TestRequest(t, logical.UpdateOperation, "role/test")
|
|
req.Data = map[string]interface{}{
|
|
"policies": []string{"policy"},
|
|
"lease": "6h",
|
|
}
|
|
resp, err := b.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
os.Setenv("NOMAD_TOKEN", svccfg.Token)
|
|
defer os.Unsetenv("NOMAD_TOKEN")
|
|
os.Setenv("NOMAD_ADDR", svccfg.URL().String())
|
|
defer os.Unsetenv("NOMAD_ADDR")
|
|
|
|
req.Operation = logical.ReadOperation
|
|
req.Path = "creds/test"
|
|
resp, err = b.HandleRequest(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("resp nil")
|
|
}
|
|
if resp.IsError() {
|
|
t.Fatalf("resp is error: %v", resp.Error())
|
|
}
|
|
}
|
|
|
|
func TestBackend_max_token_name_length(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cleanup, svccfg := prepareTestContainer(t, true)
|
|
defer cleanup()
|
|
|
|
testCases := []struct {
|
|
title string
|
|
roleName string
|
|
tokenLength int
|
|
}{
|
|
{
|
|
title: "Default",
|
|
},
|
|
{
|
|
title: "ConfigOverride",
|
|
tokenLength: 64,
|
|
},
|
|
{
|
|
title: "ConfigOverride-LongName",
|
|
roleName: "testlongerrolenametoexceed64charsdddddddddddddddddddddddd",
|
|
tokenLength: 64,
|
|
},
|
|
{
|
|
title: "Notrim",
|
|
roleName: "testlongersubrolenametoexceed64charsdddddddddddddddddddddddd",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
// setup config/access
|
|
connData := map[string]interface{}{
|
|
"address": svccfg.URL().String(),
|
|
"token": svccfg.Token,
|
|
"max_token_name_length": tc.tokenLength,
|
|
}
|
|
expected := map[string]interface{}{
|
|
"address": svccfg.URL().String(),
|
|
"max_token_name_length": tc.tokenLength,
|
|
"ca_cert": "",
|
|
"client_cert": "",
|
|
}
|
|
|
|
expectedMaxTokenNameLength := maxTokenNameLength
|
|
if tc.tokenLength != 0 {
|
|
expectedMaxTokenNameLength = tc.tokenLength
|
|
}
|
|
|
|
confReq := logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/access",
|
|
Storage: config.StorageView,
|
|
Data: connData,
|
|
}
|
|
|
|
resp, err := b.HandleRequest(context.Background(), &confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
confReq.Operation = logical.ReadOperation
|
|
resp, err = b.HandleRequest(context.Background(), &confReq)
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
|
|
}
|
|
|
|
// verify token length is returned in the config/access query
|
|
if !reflect.DeepEqual(expected, resp.Data) {
|
|
t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
|
|
}
|
|
// verify token is not returned
|
|
if resp.Data["token"] != nil {
|
|
t.Fatalf("token should not be set in the response")
|
|
}
|
|
|
|
// create a role to create nomad credentials with
|
|
// Seeds random with current timestamp
|
|
|
|
if tc.roleName == "" {
|
|
tc.roleName = "test"
|
|
}
|
|
roleTokenName := testhelpers.RandomWithPrefix(tc.roleName)
|
|
|
|
confReq.Path = "role/" + roleTokenName
|
|
confReq.Operation = logical.UpdateOperation
|
|
confReq.Data = map[string]interface{}{
|
|
"policies": []string{"policy"},
|
|
"lease": "6h",
|
|
}
|
|
resp, err = b.HandleRequest(context.Background(), &confReq)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
confReq.Operation = logical.ReadOperation
|
|
confReq.Path = "creds/" + roleTokenName
|
|
resp, err = b.HandleRequest(context.Background(), &confReq)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("resp nil")
|
|
}
|
|
if resp.IsError() {
|
|
t.Fatalf("resp is error: %v", resp.Error())
|
|
}
|
|
|
|
// extract the secret, so we can query nomad directly
|
|
generatedSecret := resp.Secret
|
|
generatedSecret.TTL = 6 * time.Hour
|
|
|
|
var d struct {
|
|
Token string `mapstructure:"secret_id"`
|
|
Accessor string `mapstructure:"accessor_id"`
|
|
}
|
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Build a client and verify that the credentials work
|
|
nomadapiConfig := nomadapi.DefaultConfig()
|
|
nomadapiConfig.Address = connData["address"].(string)
|
|
nomadapiConfig.SecretID = d.Token
|
|
client, err := nomadapi.NewClient(nomadapiConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// default query options for Nomad queries ... not sure if needed
|
|
qOpts := &nomadapi.QueryOptions{
|
|
Namespace: "default",
|
|
}
|
|
|
|
// connect to Nomad and verify the token name does not exceed the
|
|
// max_token_name_length
|
|
token, _, err := client.ACLTokens().Self(qOpts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(token.Name) > expectedMaxTokenNameLength {
|
|
t.Fatalf("token name exceeds max length (%d): %s (%d)", expectedMaxTokenNameLength, token.Name, len(token.Name))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
const caCert = `-----BEGIN CERTIFICATE-----
|
|
MIIF7zCCA9egAwIBAgIINVVQic4bju8wDQYJKoZIhvcNAQELBQAwaDELMAkGA1UE
|
|
BhMCVVMxFDASBgNVBAoMC1Vuc3BlY2lmaWVkMR8wHQYDVQQLDBZjYS0zODQzMDY2
|
|
NDA5ODI5MjQwNTU5MSIwIAYDVQQDDBl4cHMxNS5sb2NhbC5jaXBoZXJib3kuY29t
|
|
MB4XDTIyMDYwMjIxMTgxN1oXDTIzMDcwNTIxMTgxN1owaDELMAkGA1UEBhMCVVMx
|
|
FDASBgNVBAoMC1Vuc3BlY2lmaWVkMR8wHQYDVQQLDBZjYS0zODQzMDY2NDA5ODI5
|
|
MjQwNTU5MSIwIAYDVQQDDBl4cHMxNS5sb2NhbC5jaXBoZXJib3kuY29tMIICIjAN
|
|
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA35VilgfqMUKhword7wORXRFyPbpz
|
|
8uqO7eRaylMnkAkbk5eoQB/iYfXjJ6ZBs5mJGQVz5ZNvh9EzZsk1J6wqYgbwVKUx
|
|
fh4kvW6sXtDirtb4ZQAK7OTLEoapUQGnGcvm+aEYfvC1sTBl4fbex7yyN5FYMJTM
|
|
TAUumhdq2pwujaj2xkN9DwZa89Tk7tbj9HE9DTRji7bnciEtrmTAOIOfOrT/1l3x
|
|
YW1BwYXpQ0TamJ58pC/iNgEp5FAxKt9d3RggesMA7pvG/f8fNgsa/Tku/PeEXNPA
|
|
+Yx4CcAipujmqpBKiKwJ6TOzp80m2zrZ7Da4Av5vVS5GsNJxhFYD1h8hU1ptK9BS
|
|
2CaTwBpV421C9BfEmtSAksGDIWYujfiHb6XNaQrt8Hu85GBuPUudVn0lpoXLn2xD
|
|
rGK8WEK2gWZ4eez3ZDLbpLui6c1m7AVlMtj374s+LHcD7JIxY475Na7pXmEWReqM
|
|
RUyCEq1spOOn70fOdhphhmpY6DoklOTOriPawCLNmkPWRnhrIwqyP1gse9YMqQ2n
|
|
LhWUkv/08m/0pb4e5ijVhsZNzv+1PXPWCk968nzt0BMDgJT+0ZiXsaU7FILXuo7Y
|
|
Ijgrj7dpXWx2MBdMGPFQdveog7Pa80Yb7r4ERW0DL78TxYC6m/S1p14PHwZpDZzQ
|
|
LrPrBcpI5XzI7osCAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAqQwDAYDVR0TBAUw
|
|
AwEB/zA0BgNVHR4ELTAroCkwG4IZeHBzMTUubG9jYWwuY2lwaGVyYm95LmNvbTAK
|
|
hwh/AAAB/wAAADAkBgNVHREEHTAbghl4cHMxNS5sb2NhbC5jaXBoZXJib3kuY29t
|
|
MB0GA1UdDgQWBBR3bHgDp5RpzerMKRkaGDFN/ZeImjANBgkqhkiG9w0BAQsFAAOC
|
|
AgEArkuDYYWYHYxIoTeZkQz5L1y0H27ZWPJx5jBBuktPonDLQxBGAwkl6NZbJGLU
|
|
v+usII+eyjPKIgjhCiTXJAmeIngwWoN3PHOLMIPe9axuNt6qVoP4dQtzfpPR3buK
|
|
CWj9i3H0ixK73klk7QWZiBUDinYfEMSNRpU3G7NsqmqCXD4s5gB+8y9c7+zIiJyN
|
|
IaJBWpzI4eQBi/4cBhtM7Xa+CMB/8whhWYR6H+GXGZdNcP5f7bwneMstWKceTadk
|
|
IEzFucJHDySpEkIA2A9t33pV54FmEp+JVwvxAH4FABCnjPmhg0j1IonWV5pySWpG
|
|
hhEZpnRRH1XfpTA5i6dlyUA5DJjL8X1lYrgOK+LaoR52mQh5JBsMoVHFzN50DiMA
|
|
RTsbq4Qzozf23hU1BqW4NOzPTukgSGEcbT/DhXKPPPLL8JD0rPelJPq76X3TJjgZ
|
|
C9uMnZaDnxjppDXp5oBIXqC05FDxJ5sSODNOpKGyuzOU2qQLMau33yYOgaSAttBk
|
|
r29+LNFJ+0QzMuPjYXPznpxbsI+lrlZ3F2tDGGs8+JVceC1YX+cBEsEOiqNGTIip
|
|
/DY3b9gu5oiTwhcFyQW8+WFsirRS/g5t+M40WLKVPdK09z96krFXQMkL6a7LHLY1
|
|
n9ivwj+sTG1XmJYXp8naLg4wdzIUf2fJxaFNI5Yq4elZ8sY=
|
|
-----END CERTIFICATE-----`
|
|
|
|
const clientCert = `-----BEGIN CERTIFICATE-----
|
|
MIIEsDCCApigAwIBAgIIRY1JBRIynFYwDQYJKoZIhvcNAQELBQAwaDELMAkGA1UE
|
|
BhMCVVMxFDASBgNVBAoMC1Vuc3BlY2lmaWVkMR8wHQYDVQQLDBZjYS0zODQzMDY2
|
|
NDA5ODI5MjQwNTU5MSIwIAYDVQQDDBl4cHMxNS5sb2NhbC5jaXBoZXJib3kuY29t
|
|
MB4XDTIyMDYwMjIxMTgxOFoXDTIzMDcwNTIxMTgxOFowRzELMAkGA1UEBhMCVVMx
|
|
FDASBgNVBAoMC1Vuc3BlY2lmaWVkMSIwIAYDVQQDDBl4cHMxNS5sb2NhbC5jaXBo
|
|
ZXJib3kuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs+XYhsW2
|
|
vTwN7gY3xMxgbNN8d3aoeqCswOp05BBf0Vgv3febahm422ubXXd5Mg2UGiU7sJVe
|
|
4tUpDeupVVRX5Qr/hpiXgEyfRDAAAJKqrl65KSS62TCbT/eJZ0ah25HV1evI4uM2
|
|
0kl5QWhtQjDyaVlTS38YFqXXQvpOuU5DG6UbKnpMcpsCPTyUKEJvJ95ZLcz0HJ8I
|
|
kIHrnX0Lt0pOhkllj5Nk4cXhU8CFk8IGNz7SVAycrUsffAUMNNEbrIOIfOTPHR1c
|
|
q3X9hO4/5pt80uIDMFwwumoA7nQR0AhlKkw9SskCIzJhKwKwssQY7fmovNG0fOEd
|
|
/+vSHK7OsYW+gwIDAQABo38wfTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYI
|
|
KwYBBQUHAwIwCQYDVR0TBAIwADAqBgNVHREEIzAhghl4cHMxNS5sb2NhbC5jaXBo
|
|
ZXJib3kuY29thwR/AAABMB8GA1UdIwQYMBaAFHdseAOnlGnN6swpGRoYMU39l4ia
|
|
MA0GCSqGSIb3DQEBCwUAA4ICAQBUSP4ZJglCCrYkM5Le7McdvfkM5uYv1aQn0sM4
|
|
gbyDEWO0fnv50vLpD3y4ckgHLoD52pAZ0hN8a7rwAUae21GA6DvEchSH5x/yvJiS
|
|
7FBlq39sAafe03ZlzDErNYJRkLcnPAqG74lJ1SSsMcs9gCPHM8R7HtNnhAga06L7
|
|
K8/G43dsGZCmEb+xcX2B9McCt8jBG6TJPTGafb3BJ0JTmR/tHdoLFIiNwI+qzd2U
|
|
lMnGlkIApULX8tmIMsWO0rjdiFkPWGcmfn9ChC0iDpQOAcKSDBcZlWrDNpzKk0mK
|
|
l0TbE6cxcmCUUpiwaXFrbkwVWQw4W0c4b3sWFtWifFbiR1qZ/OT2Y2sHbkbxwvPl
|
|
PjjXMDBAdRRwtNcTP1E55I5zvwzzBxUpxOob0miorhTJrZR9So0rgv7Roce4ED6M
|
|
WETYa/mGhe+Q7gBQygIVoryfQLgGBsHC+7V4RDvYTazwZkz9nLQxHLI/TAZU5ofM
|
|
WqdoUkMd68rxTTEUoMfGbftxjKA0raxGcO7/PjLR3O743EwCqeqYJ7OKWgGRLnui
|
|
kIKNUJlZ9umURUFzL++Bx4Pr95jWXb2WYqYYQxhDz0oR5q5smnFm5+/1/MLDMvDU
|
|
TrgBK6pey4QF33B/I55H1+7tGdv85Q57Z8UrNi/IQxR2sFlsOTeCwStpBQ56sdZk
|
|
Wi4+cQ==
|
|
-----END CERTIFICATE-----`
|
|
|
|
const clientKey = `-----BEGIN PRIVATE KEY-----
|
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCz5diGxba9PA3u
|
|
BjfEzGBs03x3dqh6oKzA6nTkEF/RWC/d95tqGbjba5tdd3kyDZQaJTuwlV7i1SkN
|
|
66lVVFflCv+GmJeATJ9EMAAAkqquXrkpJLrZMJtP94lnRqHbkdXV68ji4zbSSXlB
|
|
aG1CMPJpWVNLfxgWpddC+k65TkMbpRsqekxymwI9PJQoQm8n3lktzPQcnwiQgeud
|
|
fQu3Sk6GSWWPk2ThxeFTwIWTwgY3PtJUDJytSx98BQw00Rusg4h85M8dHVyrdf2E
|
|
7j/mm3zS4gMwXDC6agDudBHQCGUqTD1KyQIjMmErArCyxBjt+ai80bR84R3/69Ic
|
|
rs6xhb6DAgMBAAECggEAPBcja2kxcCZWNNKo4DiwYMmHwtPE1SlEazAlmWSKzP+b
|
|
BZbGt/sdj1VzURYuSnTUqqMTPBm41yYCj57PMix5K42v6sKfoIB3lqw94/MZxiLn
|
|
0IFvVErzJhP2NqQWPqSI++rFcFwbHMTkFuAN1tVIs73dn9M1NaNxsvKvRyCIM/wz
|
|
5YQSDyTkdW4jQM2RvUFOoqwmeyAlQoBRMgQ4bHfLHxmPEjFgw1MAmmG8bJdkupin
|
|
MVzhZyKj4Fh80Xa2MU4KokijjG41hmYbg/sjNHaHJFDA92Rwq13dhWytrauJDxa/
|
|
3yj8pHWc23Y3hXvRAf/cibDVzXmmLj49W1i06KuUCQKBgQDj5yF/DJV0IOkhfbol
|
|
+f5AGH4ZrEXA/JwA5SxHU+aKhUuPEqK/LeUWqiy3szFjOz2JOnCC0LMN42nsmMyK
|
|
sdQEKHp2SPd2wCxsAKZAuxrEi6yBt1mEPFFU5yzvZbdMqYChKJjm9fbRHtuc63s8
|
|
PyVw67Ii9o4ij+PxfTobIs18xwKBgQDKE59w3uUDt2uoqNC8x4m5onL2p2vtcTHC
|
|
CxU57mu1+9CRM8N2BEp2VI5JaXjqt6W4u9ISrmOqmsPgTwosAquKpA/nu3bVvR9g
|
|
WlN9dh2Xgza0/AFaA9CB++ier8RJq5xFlcasMUmgkhYt3zgKNgRDfjfREWM0yamm
|
|
P++hAYRcZQKBgHEuYQk6k6J3ka/rQ54GmEj2oPFZB88+5K7hIWtO9IhIiGzGYYK2
|
|
ZTYrT0fvuxA/5GCZYDTnNnUoQnuYqsQaamOiQqcpt5QG/kiozegJw9JmV0aYauFs
|
|
HyweHsfJaQ2uhE4E3mKdNnVGcORuYeZaqdp5gx8v+QibEyXj/g5p60kTAoGBALKp
|
|
TMOHXmW9yqKwtvThWoRU+13WQlcJSFvuXpL8mCCrBgkLAhqaypb6RV7ksLKdMhk1
|
|
fhNkOdxBv0LXvv+QUMhgK2vP084/yrjuw3hecOVfboPvduZ2DuiNp2p9rocQAjeH
|
|
p8LgRN+Bqbhe7fYhMf3WX1UqEVM/pQ3G43+vjq39AoGAOyD2/hFSIx6BMddUNTHG
|
|
BEsMUc/DHYslZebbF1zAWnkKdTt+URhtHAFB2tYRDgkZfwW+wr/w12dJTIkX965o
|
|
HO7tI4FgpU9b0i8FTuwYkBfjwp2j0Xd2/VBR8Qpd17qKl3I6NXDsf3ykjGZAvldH
|
|
Tll+qwEZpXSRa5OWWTpGV8I=
|
|
-----END PRIVATE KEY-----`
|