mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
715 lines
22 KiB
Go
715 lines
22 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package nomad
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
nomadapi "github.com/hashicorp/nomad/api"
|
|
"github.com/hashicorp/vault/helper/testhelpers"
|
|
"github.com/hashicorp/vault/sdk/helper/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) {
|
|
// Skipping on ARM, as this image can't run on ARM architecture
|
|
if strings.Contains(runtime.GOARCH, "arm") {
|
|
t.Skip("Skipping, as this image is not supported on ARM architectures")
|
|
}
|
|
|
|
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: "docker.mirror.hashicorp.services/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-----`
|