1
0

almost completely remove testing code

things gone wild
This commit is contained in:
Konstantin Demin 2024-07-02 12:32:04 +03:00
parent dda95e4c57
commit c3f569436c
1282 changed files with 13 additions and 287883 deletions

View File

@ -1,28 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"fmt"
"net"
"net/http"
"testing"
)
// testHTTPServer creates a test HTTP server that handles requests until
// the listener returned is closed.
func testHTTPServer(t *testing.T, handler http.Handler) (*Config, net.Listener) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %s", err)
}
server := &http.Server{Handler: handler}
go server.Serve(ln)
config := DefaultConfig()
config.Address = fmt.Sprintf("http://%s", ln.Addr())
return config, ln
}

View File

@ -1,135 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package approle
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strings"
"testing"
"github.com/hashicorp/vault/api"
)
// testHTTPServer creates a test HTTP server that handles requests until
// the listener returned is closed.
func testHTTPServer(
t *testing.T, handler http.Handler,
) (*api.Config, net.Listener) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %s", err)
}
server := &http.Server{Handler: handler}
go server.Serve(ln)
config := api.DefaultConfig()
config.Address = fmt.Sprintf("http://%s", ln.Addr())
return config, ln
}
func init() {
os.Setenv("VAULT_TOKEN", "")
}
func TestLogin(t *testing.T) {
secretIDEnvVar := "APPROLE_SECRET_ID"
allowedRoleID := "my-role-id"
allowedSecretID := "my-secret-id"
content := []byte(allowedSecretID)
tmpfile, err := os.CreateTemp("", "file-containing-secret-id")
if err != nil {
t.Fatalf("error creating temp file: %v", err)
}
defer os.Remove(tmpfile.Name()) // clean up
err = os.Setenv(secretIDEnvVar, allowedSecretID)
if err != nil {
t.Fatalf("error writing secret ID to env var: %v", err)
}
if _, err := tmpfile.Write(content); err != nil {
t.Fatalf("error writing to temp file: %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("error closing temp file: %v", err)
}
// a response to return if the correct values were passed to login
authSecret := &api.Secret{
Auth: &api.SecretAuth{
ClientToken: "a-client-token",
},
}
authBytes, err := json.Marshal(authSecret)
if err != nil {
t.Fatalf("error marshaling json: %v", err)
}
handler := func(w http.ResponseWriter, req *http.Request) {
payload := make(map[string]interface{})
err := json.NewDecoder(req.Body).Decode(&payload)
if err != nil {
t.Fatalf("error decoding json: %v", err)
}
if payload["role_id"] == allowedRoleID && payload["secret_id"] == allowedSecretID {
w.Write(authBytes)
}
}
config, ln := testHTTPServer(t, http.HandlerFunc(handler))
defer ln.Close()
config.Address = strings.ReplaceAll(config.Address, "127.0.0.1", "localhost")
client, err := api.NewClient(config)
if err != nil {
t.Fatalf("error initializing Vault client: %v", err)
}
authFromFile, err := NewAppRoleAuth(allowedRoleID, &SecretID{FromFile: tmpfile.Name()})
if err != nil {
t.Fatalf("error initializing AppRoleAuth with secret ID file: %v", err)
}
loginRespFromFile, err := client.Auth().Login(context.TODO(), authFromFile)
if err != nil {
t.Fatalf("error logging in with secret ID from file: %v", err)
}
if loginRespFromFile.Auth == nil || loginRespFromFile.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login")
}
authFromEnv, err := NewAppRoleAuth(allowedRoleID, &SecretID{FromEnv: secretIDEnvVar})
if err != nil {
t.Fatalf("error initializing AppRoleAuth with secret ID env var: %v", err)
}
loginRespFromEnv, err := client.Auth().Login(context.TODO(), authFromEnv)
if err != nil {
t.Fatalf("error logging in with secret ID from env var: %v", err)
}
if loginRespFromEnv.Auth == nil || loginRespFromEnv.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login with secret ID from env var")
}
authFromStr, err := NewAppRoleAuth(allowedRoleID, &SecretID{FromString: allowedSecretID})
if err != nil {
t.Fatalf("error initializing AppRoleAuth with secret ID string: %v", err)
}
loginRespFromStr, err := client.Auth().Login(context.TODO(), authFromStr)
if err != nil {
t.Fatalf("error logging in with string: %v", err)
}
if loginRespFromStr.Auth == nil || loginRespFromStr.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login with secret ID from string")
}
}

View File

@ -1,159 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package ldap
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strings"
"testing"
"github.com/hashicorp/vault/api"
)
// testHTTPServer creates a test HTTP server that handles requests until
// the listener returned is closed.
func testHTTPServer(
t *testing.T, handler http.Handler,
) (*api.Config, net.Listener) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %s", err)
}
server := &http.Server{Handler: handler}
go server.Serve(ln)
config := api.DefaultConfig()
config.Address = fmt.Sprintf("http://%s", ln.Addr())
return config, ln
}
func init() {
os.Setenv("VAULT_TOKEN", "")
}
func TestLogin(t *testing.T) {
passwordEnvVar := "LDAP_PASSWORD"
allowedPassword := "6hrtL!*bro!ywbQbvDwW"
content := []byte(allowedPassword)
tmpfile, err := os.CreateTemp("./", "file-containing-password")
if err != nil {
t.Fatalf("error creating temp file: %v", err)
}
defer os.Remove(tmpfile.Name()) // clean up
err = os.Setenv(passwordEnvVar, allowedPassword)
if err != nil {
t.Fatalf("error writing password to env var: %v", err)
}
if _, err := tmpfile.Write(content); err != nil {
t.Fatalf("error writing to temp file: %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("error closing temp file: %v", err)
}
// a response to return if the correct values were passed to login
authSecret := &api.Secret{
Auth: &api.SecretAuth{
ClientToken: "a-client-token",
},
}
authBytes, err := json.Marshal(authSecret)
if err != nil {
t.Fatalf("error marshaling json: %v", err)
}
handler := func(w http.ResponseWriter, req *http.Request) {
payload := make(map[string]interface{})
err := json.NewDecoder(req.Body).Decode(&payload)
if err != nil {
t.Fatalf("error decoding json: %v", err)
}
if payload["password"] == allowedPassword {
w.Write(authBytes)
}
}
config, ln := testHTTPServer(t, http.HandlerFunc(handler))
defer ln.Close()
config.Address = strings.ReplaceAll(config.Address, "127.0.0.1", "localhost")
client, err := api.NewClient(config)
if err != nil {
t.Fatalf("error initializing Vault client: %v", err)
}
// Password fromFile test
authFromFile, err := NewLDAPAuth("my-ldap-username", &Password{FromFile: tmpfile.Name()})
if err != nil {
t.Fatalf("error initializing LDAPAuth with password file: %v", err)
}
loginRespFromFile, err := client.Auth().Login(context.TODO(), authFromFile)
if err != nil {
t.Fatalf("error logging in with password from file: %v", err)
}
if loginRespFromFile.Auth == nil || loginRespFromFile.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login")
}
// Password fromEnv Test
authFromEnv, err := NewLDAPAuth("my-ldap-username", &Password{FromEnv: passwordEnvVar})
if err != nil {
t.Fatalf("error initializing LDAPAuth with password env var: %v", err)
}
loginRespFromEnv, err := client.Auth().Login(context.TODO(), authFromEnv)
if err != nil {
t.Fatalf("error logging in with password from env var: %v", err)
}
if loginRespFromEnv.Auth == nil || loginRespFromEnv.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login with password from env var")
}
// Password fromStr test
authFromStr, err := NewLDAPAuth("my-ldap-username", &Password{FromString: allowedPassword})
if err != nil {
t.Fatalf("error initializing LDAPAuth with password string: %v", err)
}
loginRespFromStr, err := client.Auth().Login(context.TODO(), authFromStr)
if err != nil {
t.Fatalf("error logging in with string: %v", err)
}
if loginRespFromStr.Auth == nil || loginRespFromStr.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login with password from string")
}
// Empty User Test
_, err = NewLDAPAuth("", &Password{FromString: allowedPassword})
if err.Error() != "no user name provided for login" {
t.Fatalf("Auth object created for empty username: %v", err)
}
// Empty Password Test
_, err = NewLDAPAuth("my-ldap-username", nil)
if err.Error() != "no password provided for login" {
t.Fatalf("Auth object created when passing a nil Password struct: %v", err)
}
// Auth with Custom MountPath
ldapMount := WithMountPath("customMount")
_, err = NewLDAPAuth("my-ldap-username", &Password{FromString: allowedPassword}, ldapMount)
if err != nil {
t.Fatalf("error initializing LDAPAuth with custom mountpath: %v", err)
}
}

View File

@ -1,134 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package userpass
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strings"
"testing"
"github.com/hashicorp/vault/api"
)
// testHTTPServer creates a test HTTP server that handles requests until
// the listener returned is closed.
func testHTTPServer(
t *testing.T, handler http.Handler,
) (*api.Config, net.Listener) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %s", err)
}
server := &http.Server{Handler: handler}
go server.Serve(ln)
config := api.DefaultConfig()
config.Address = fmt.Sprintf("http://%s", ln.Addr())
return config, ln
}
func init() {
os.Setenv("VAULT_TOKEN", "")
}
func TestLogin(t *testing.T) {
passwordEnvVar := "USERPASS_PASSWORD"
allowedPassword := "my-password"
content := []byte(allowedPassword)
tmpfile, err := os.CreateTemp("", "file-containing-password")
if err != nil {
t.Fatalf("error creating temp file: %v", err)
}
defer os.Remove(tmpfile.Name()) // clean up
err = os.Setenv(passwordEnvVar, allowedPassword)
if err != nil {
t.Fatalf("error writing password to env var: %v", err)
}
if _, err := tmpfile.Write(content); err != nil {
t.Fatalf("error writing to temp file: %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("error closing temp file: %v", err)
}
// a response to return if the correct values were passed to login
authSecret := &api.Secret{
Auth: &api.SecretAuth{
ClientToken: "a-client-token",
},
}
authBytes, err := json.Marshal(authSecret)
if err != nil {
t.Fatalf("error marshaling json: %v", err)
}
handler := func(w http.ResponseWriter, req *http.Request) {
payload := make(map[string]interface{})
err := json.NewDecoder(req.Body).Decode(&payload)
if err != nil {
t.Fatalf("error decoding json: %v", err)
}
if payload["password"] == allowedPassword {
w.Write(authBytes)
}
}
config, ln := testHTTPServer(t, http.HandlerFunc(handler))
defer ln.Close()
config.Address = strings.ReplaceAll(config.Address, "127.0.0.1", "localhost")
client, err := api.NewClient(config)
if err != nil {
t.Fatalf("error initializing Vault client: %v", err)
}
authFromFile, err := NewUserpassAuth("my-role-id", &Password{FromFile: tmpfile.Name()})
if err != nil {
t.Fatalf("error initializing AppRoleAuth with password file: %v", err)
}
loginRespFromFile, err := client.Auth().Login(context.TODO(), authFromFile)
if err != nil {
t.Fatalf("error logging in with password from file: %v", err)
}
if loginRespFromFile.Auth == nil || loginRespFromFile.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login")
}
authFromEnv, err := NewUserpassAuth("my-role-id", &Password{FromEnv: passwordEnvVar})
if err != nil {
t.Fatalf("error initializing AppRoleAuth with password env var: %v", err)
}
loginRespFromEnv, err := client.Auth().Login(context.TODO(), authFromEnv)
if err != nil {
t.Fatalf("error logging in with password from env var: %v", err)
}
if loginRespFromEnv.Auth == nil || loginRespFromEnv.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login with password from env var")
}
authFromStr, err := NewUserpassAuth("my-role-id", &Password{FromString: allowedPassword})
if err != nil {
t.Fatalf("error initializing AppRoleAuth with password string: %v", err)
}
loginRespFromStr, err := client.Auth().Login(context.TODO(), authFromStr)
if err != nil {
t.Fatalf("error logging in with string: %v", err)
}
if loginRespFromStr.Auth == nil || loginRespFromStr.Auth.ClientToken == "" {
t.Fatalf("no authentication info returned by login with password from string")
}
}

View File

@ -1,131 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"context"
"testing"
)
type mockAuthMethod struct {
mockedSecret *Secret
mockedError error
}
func (m *mockAuthMethod) Login(_ context.Context, _ *Client) (*Secret, error) {
return m.mockedSecret, m.mockedError
}
func TestAuth_Login(t *testing.T) {
a := &Auth{
c: &Client{},
}
m := mockAuthMethod{
mockedSecret: &Secret{
Auth: &SecretAuth{
ClientToken: "a-client-token",
},
},
mockedError: nil,
}
t.Run("Login should set token on success", func(t *testing.T) {
if a.c.Token() != "" {
t.Errorf("client token was %v expected to be unset", a.c.Token())
}
_, err := a.Login(context.Background(), &m)
if err != nil {
t.Errorf("Login() error = %v", err)
return
}
if a.c.Token() != m.mockedSecret.Auth.ClientToken {
t.Errorf("client token was %v expected %v", a.c.Token(), m.mockedSecret.Auth.ClientToken)
return
}
})
}
func TestAuth_MFALoginSinglePhase(t *testing.T) {
t.Run("MFALogin() should succeed if credentials are passed in", func(t *testing.T) {
a := &Auth{
c: &Client{},
}
m := mockAuthMethod{
mockedSecret: &Secret{
Auth: &SecretAuth{
ClientToken: "a-client-token",
},
},
mockedError: nil,
}
_, err := a.MFALogin(context.Background(), &m, "testMethod:testPasscode")
if err != nil {
t.Errorf("MFALogin() error %v", err)
return
}
if a.c.Token() != m.mockedSecret.Auth.ClientToken {
t.Errorf("client token was %v expected %v", a.c.Token(), m.mockedSecret.Auth.ClientToken)
return
}
})
}
func TestAuth_MFALoginTwoPhase(t *testing.T) {
tests := []struct {
name string
a *Auth
m *mockAuthMethod
creds *string
wantErr bool
}{
{
name: "return MFARequirements",
a: &Auth{
c: &Client{},
},
m: &mockAuthMethod{
mockedSecret: &Secret{
Auth: &SecretAuth{
MFARequirement: &MFARequirement{
MFARequestID: "a-req-id",
MFAConstraints: nil,
},
},
},
},
wantErr: false,
},
{
name: "error if no MFARequirements",
a: &Auth{
c: &Client{},
},
m: &mockAuthMethod{
mockedSecret: &Secret{
Auth: &SecretAuth{},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secret, err := tt.a.MFALogin(context.Background(), tt.m)
if (err != nil) != tt.wantErr {
t.Errorf("MFALogin() error = %v, wantErr %v", err, tt.wantErr)
return
}
if secret.Auth.MFARequirement != tt.m.mockedSecret.Auth.MFARequirement {
t.Errorf("MFALogin() returned %v, expected %v", secret.Auth.MFARequirement, tt.m.mockedSecret.Auth.MFARequirement)
return
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,390 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"reflect"
"testing"
"time"
)
func TestExtractVersionMetadata(t *testing.T) {
t.Parallel()
inputCreatedTimeStr := "2022-05-06T23:02:04.865025Z"
inputDeletionTimeStr := "2022-06-17T01:15:03.279013Z"
expectedCreatedTimeParsed, err := time.Parse(time.RFC3339, inputCreatedTimeStr)
if err != nil {
t.Fatalf("unable to parse expected created time: %v", err)
}
expectedDeletionTimeParsed, err := time.Parse(time.RFC3339, inputDeletionTimeStr)
if err != nil {
t.Fatalf("unable to parse expected created time: %v", err)
}
testCases := []struct {
name string
input *Secret
expected *KVVersionMetadata
}{
{
name: "a secret",
input: &Secret{
Data: map[string]interface{}{
"data": map[string]interface{}{
"password": "Hashi123",
},
"metadata": map[string]interface{}{
"version": 10,
"created_time": inputCreatedTimeStr,
"deletion_time": "",
"destroyed": false,
"custom_metadata": nil,
},
},
},
expected: &KVVersionMetadata{
Version: 10,
CreatedTime: expectedCreatedTimeParsed,
DeletionTime: time.Time{},
Destroyed: false,
},
},
{
name: "a secret that has been deleted",
input: &Secret{
Data: map[string]interface{}{
"data": map[string]interface{}{
"password": "Hashi123",
},
"metadata": map[string]interface{}{
"version": 10,
"created_time": inputCreatedTimeStr,
"deletion_time": inputDeletionTimeStr,
"destroyed": false,
"custom_metadata": nil,
},
},
},
expected: &KVVersionMetadata{
Version: 10,
CreatedTime: expectedCreatedTimeParsed,
DeletionTime: expectedDeletionTimeParsed,
Destroyed: false,
},
},
{
name: "a response from a Write operation",
input: &Secret{
Data: map[string]interface{}{
"version": 10,
"created_time": inputCreatedTimeStr,
"deletion_time": "",
"destroyed": false,
"custom_metadata": nil,
},
},
expected: &KVVersionMetadata{
Version: 10,
CreatedTime: expectedCreatedTimeParsed,
DeletionTime: time.Time{},
Destroyed: false,
},
},
}
for _, tc := range testCases {
versionMetadata, err := extractVersionMetadata(tc.input)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(versionMetadata, tc.expected) {
t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, versionMetadata, tc.expected)
}
}
}
func TestExtractDataAndVersionMetadata(t *testing.T) {
t.Parallel()
inputCreatedTimeStr := "2022-05-06T23:02:04.865025Z"
inputDeletionTimeStr := "2022-06-17T01:15:03.279013Z"
expectedCreatedTimeParsed, err := time.Parse(time.RFC3339, inputCreatedTimeStr)
if err != nil {
t.Fatalf("unable to parse expected created time: %v", err)
}
expectedDeletionTimeParsed, err := time.Parse(time.RFC3339, inputDeletionTimeStr)
if err != nil {
t.Fatalf("unable to parse expected created time: %v", err)
}
readResp := &Secret{
Data: map[string]interface{}{
"data": map[string]interface{}{
"password": "Hashi123",
},
"metadata": map[string]interface{}{
"version": 10,
"created_time": inputCreatedTimeStr,
"deletion_time": "",
"destroyed": false,
"custom_metadata": nil,
},
},
}
readRespDeleted := &Secret{
Data: map[string]interface{}{
"data": nil,
"metadata": map[string]interface{}{
"version": 10,
"created_time": inputCreatedTimeStr,
"deletion_time": inputDeletionTimeStr,
"destroyed": false,
"custom_metadata": nil,
},
},
}
testCases := []struct {
name string
input *Secret
expected *KVSecret
}{
{
name: "a response from a Read operation",
input: readResp,
expected: &KVSecret{
Data: map[string]interface{}{
"password": "Hashi123",
},
VersionMetadata: &KVVersionMetadata{
Version: 10,
CreatedTime: expectedCreatedTimeParsed,
DeletionTime: time.Time{},
Destroyed: false,
},
// it's tempting to test some Secrets with custom_metadata but
// we can't in this test because it isn't until we call the
// extractCustomMetadata function that the custom metadata
// gets added onto the struct. See TestExtractCustomMetadata.
CustomMetadata: nil,
Raw: readResp,
},
},
{
name: "a secret that has been deleted and thus has nil data",
input: readRespDeleted,
expected: &KVSecret{
Data: nil,
VersionMetadata: &KVVersionMetadata{
Version: 10,
CreatedTime: expectedCreatedTimeParsed,
DeletionTime: expectedDeletionTimeParsed,
Destroyed: false,
},
CustomMetadata: nil,
Raw: readRespDeleted,
},
},
}
for _, tc := range testCases {
dvm, err := extractDataAndVersionMetadata(tc.input)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(dvm, tc.expected) {
t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, dvm, tc.expected)
}
}
}
func TestExtractFullMetadata(t *testing.T) {
inputCreatedTimeStr := "2022-05-20T00:51:49.419794Z"
expectedCreatedTimeParsed, err := time.Parse(time.RFC3339, inputCreatedTimeStr)
if err != nil {
t.Fatalf("unable to parse expected created time: %v", err)
}
inputUpdatedTimeStr := "2022-05-20T20:23:43.284488Z"
expectedUpdatedTimeParsed, err := time.Parse(time.RFC3339, inputUpdatedTimeStr)
if err != nil {
t.Fatalf("unable to parse expected updated time: %v", err)
}
inputDeletedTimeStr := "2022-05-21T00:05:49.521697Z"
expectedDeletedTimeParsed, err := time.Parse(time.RFC3339, inputDeletedTimeStr)
if err != nil {
t.Fatalf("unable to parse expected deletion time: %v", err)
}
metadataResp := &Secret{
Data: map[string]interface{}{
"cas_required": true,
"created_time": inputCreatedTimeStr,
"current_version": 2,
"custom_metadata": map[string]interface{}{
"org": "eng",
},
"delete_version_after": "200s",
"max_versions": 3,
"oldest_version": 1,
"updated_time": inputUpdatedTimeStr,
"versions": map[string]interface{}{
"2": map[string]interface{}{
"created_time": inputUpdatedTimeStr,
"deletion_time": "",
"destroyed": false,
},
"1": map[string]interface{}{
"created_time": inputCreatedTimeStr,
"deletion_time": inputDeletedTimeStr,
"destroyed": false,
},
},
},
}
testCases := []struct {
name string
input *Secret
expected *KVMetadata
}{
{
name: "a metadata response",
input: metadataResp,
expected: &KVMetadata{
CASRequired: true,
CreatedTime: expectedCreatedTimeParsed,
CurrentVersion: 2,
CustomMetadata: map[string]interface{}{
"org": "eng",
},
DeleteVersionAfter: time.Duration(200 * time.Second),
MaxVersions: 3,
OldestVersion: 1,
UpdatedTime: expectedUpdatedTimeParsed,
Versions: map[string]KVVersionMetadata{
"2": {
Version: 2,
CreatedTime: expectedUpdatedTimeParsed,
DeletionTime: time.Time{},
},
"1": {
Version: 1,
CreatedTime: expectedCreatedTimeParsed,
DeletionTime: expectedDeletedTimeParsed,
},
},
},
},
}
for _, tc := range testCases {
md, err := extractFullMetadata(tc.input)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(md, tc.expected) {
t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, md, tc.expected)
}
}
}
func TestExtractCustomMetadata(t *testing.T) {
testCases := []struct {
name string
inputAPIResp *Secret
expected map[string]interface{}
}{
{
name: "a read response with some custom metadata",
inputAPIResp: &Secret{
Data: map[string]interface{}{
"metadata": map[string]interface{}{
"custom_metadata": map[string]interface{}{"org": "eng"},
},
},
},
expected: map[string]interface{}{"org": "eng"},
},
{
name: "a write response with some (pre-existing) custom metadata",
inputAPIResp: &Secret{
Data: map[string]interface{}{
"custom_metadata": map[string]interface{}{"org": "eng"},
},
},
expected: map[string]interface{}{"org": "eng"},
},
{
name: "a read response with no custom metadata from a pre-1.9 Vault server",
inputAPIResp: &Secret{
Data: map[string]interface{}{
"metadata": map[string]interface{}{},
},
},
expected: map[string]interface{}(nil),
},
{
name: "a write response with no custom metadata from a pre-1.9 Vault server",
inputAPIResp: &Secret{
Data: map[string]interface{}{},
},
expected: map[string]interface{}(nil),
},
{
name: "a read response with no custom metadata from a post-1.9 Vault server",
inputAPIResp: &Secret{
Data: map[string]interface{}{
"metadata": map[string]interface{}{
"custom_metadata": nil,
},
},
},
expected: map[string]interface{}(nil),
},
{
name: "a write response with no custom metadata from a post-1.9 Vault server",
inputAPIResp: &Secret{
Data: map[string]interface{}{
"custom_metadata": nil,
},
},
expected: map[string]interface{}(nil),
},
{
name: "a read response where custom metadata was deleted",
inputAPIResp: &Secret{
Data: map[string]interface{}{
"metadata": map[string]interface{}{
"custom_metadata": map[string]interface{}{},
},
},
},
expected: map[string]interface{}{},
},
{
name: "a write response where custom metadata was deleted",
inputAPIResp: &Secret{
Data: map[string]interface{}{
"custom_metadata": map[string]interface{}{},
},
},
expected: map[string]interface{}{},
},
}
for _, tc := range testCases {
cm := extractCustomMetadata(tc.inputAPIResp)
if !reflect.DeepEqual(cm, tc.expected) {
t.Fatalf("%s: got\n%#v\nexpected\n%#v\n", tc.name, cm, tc.expected)
}
}
}

View File

@ -1,83 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"net/http"
"net/url"
"testing"
)
func TestBuildSamplePolicy(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
req *OutputPolicyError
expected string
err error
}{
{
"happy path",
&OutputPolicyError{
method: http.MethodGet,
path: "/something",
},
formatOutputPolicy("/something", []string{"read"}),
nil,
},
{ // test included to clear up some confusion around the sanitize comment
"demonstrate that this function does not format fully",
&OutputPolicyError{
method: http.MethodGet,
path: "http://vault.test/v1/something",
},
formatOutputPolicy("http://vault.test/v1/something", []string{"read"}),
nil,
},
{ // test that list is properly returned
"list over read returned",
&OutputPolicyError{
method: http.MethodGet,
path: "/something",
params: url.Values{
"list": []string{"true"},
},
},
formatOutputPolicy("/something", []string{"list"}),
nil,
},
{
"valid protected path",
&OutputPolicyError{
method: http.MethodGet,
path: "/sys/config/ui/headers/",
},
formatOutputPolicy("/sys/config/ui/headers/", []string{"read", "sudo"}),
nil,
},
{ // ensure that a formatted path that trims the trailing slash as the code does still works for recognizing a sudo path
"valid protected path no trailing /",
&OutputPolicyError{
method: http.MethodGet,
path: "/sys/config/ui/headers",
},
formatOutputPolicy("/sys/config/ui/headers", []string{"read", "sudo"}),
nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := tc.req.buildSamplePolicy()
if tc.err != err {
t.Fatalf("expected for the error to be %v instead got %v\n", tc.err, err)
}
if tc.expected != result {
t.Fatalf("expected for the policy string to be %v instead got %v\n", tc.expected, result)
}
})
}
}

View File

@ -1,78 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import "testing"
func TestIsSudoPath(t *testing.T) {
t.Parallel()
testCases := []struct {
path string
expected bool
}{
// Testing: Not a real endpoint
{
"/not/in/sudo/paths/list",
false,
},
// Testing: sys/raw/{path}
{
"/sys/raw/single-node-path",
true,
},
{
"/sys/raw/multiple/nodes/path",
true,
},
{
"/sys/raw/WEIRD(but_still_valid!)p4Th?🗿笑",
true,
},
// Testing: sys/auth/{path}/tune
{
"/sys/auth/path/in/middle/tune",
true,
},
// Testing: sys/plugins/catalog/{type} and sys/plugins/catalog/{name} (regexes overlap)
{
"/sys/plugins/catalog/some-type",
true,
},
// Testing: Not a real endpoint
{
"/sys/plugins/catalog/some/type/or/name/with/slashes",
false,
},
// Testing: sys/plugins/catalog/{type}/{name}
{
"/sys/plugins/catalog/some-type/some-name",
true,
},
// Testing: Not a real endpoint
{
"/sys/plugins/catalog/some-type/some/name/with/slashes",
false,
},
// Testing: auth/token/accessors (an example of a sudo path that only accepts list operations)
// It is matched as sudo without the trailing slash...
{
"/auth/token/accessors",
true,
},
// ...and also with it.
// (Although at the time of writing, the only caller of IsSudoPath always removes trailing slashes.)
{
"/auth/token/accessors/",
true,
},
}
for _, tc := range testCases {
result := IsSudoPath(tc.path)
if result != tc.expected {
t.Fatalf("expected api.IsSudoPath to return %v for path %s but it returned %v", tc.expected, tc.path, result)
}
}
}

View File

@ -1,285 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"errors"
"fmt"
"math/rand"
"reflect"
"testing"
"testing/quick"
"time"
"github.com/go-test/deep"
)
func TestRenewer_NewRenewer(t *testing.T) {
t.Parallel()
client, err := NewClient(DefaultConfig())
if err != nil {
t.Fatal(err)
}
cases := []struct {
name string
i *RenewerInput
e *Renewer
err bool
}{
{
name: "nil",
i: nil,
e: nil,
err: true,
},
{
name: "missing_secret",
i: &RenewerInput{
Secret: nil,
},
e: nil,
err: true,
},
{
name: "default_grace",
i: &RenewerInput{
Secret: &Secret{},
},
e: &Renewer{
secret: &Secret{},
},
err: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
v, err := client.NewRenewer(tc.i)
if (err != nil) != tc.err {
t.Fatal(err)
}
if v == nil {
return
}
// Zero-out channels because reflect
v.client = nil
v.random = nil
v.doneCh = nil
v.renewCh = nil
v.stopCh = nil
if diff := deep.Equal(tc.e, v); diff != nil {
t.Error(diff)
}
})
}
}
func TestLifetimeWatcher(t *testing.T) {
t.Parallel()
client, err := NewClient(DefaultConfig())
if err != nil {
t.Fatal(err)
}
// Note that doRenewWithOptions starts its loop with an initial renewal.
// This has a big impact on the particulars of the following cases.
renewedSecret := &Secret{}
var caseOneErrorCount int
var caseManyErrorsCount int
cases := []struct {
maxTestTime time.Duration
name string
leaseDurationSeconds int
incrementSeconds int
renew renewFunc
expectError error
expectRenewal bool
}{
{
maxTestTime: time.Second,
name: "no_error",
leaseDurationSeconds: 60,
incrementSeconds: 60,
renew: func(_ string, _ int) (*Secret, error) {
return renewedSecret, nil
},
expectError: nil,
expectRenewal: true,
},
{
maxTestTime: time.Second,
name: "short_increment_duration",
leaseDurationSeconds: 60,
incrementSeconds: 10,
renew: func(_ string, _ int) (*Secret, error) {
return renewedSecret, nil
},
expectError: nil,
expectRenewal: true,
},
{
maxTestTime: 5 * time.Second,
name: "one_error",
leaseDurationSeconds: 15,
incrementSeconds: 15,
renew: func(_ string, _ int) (*Secret, error) {
if caseOneErrorCount == 0 {
caseOneErrorCount++
return nil, fmt.Errorf("renew failure")
}
return renewedSecret, nil
},
expectError: nil,
expectRenewal: true,
},
{
maxTestTime: 15 * time.Second,
name: "many_errors",
leaseDurationSeconds: 15,
incrementSeconds: 15,
renew: func(_ string, _ int) (*Secret, error) {
if caseManyErrorsCount == 3 {
return renewedSecret, nil
}
caseManyErrorsCount++
return nil, fmt.Errorf("renew failure")
},
expectError: nil,
expectRenewal: true,
},
{
maxTestTime: 15 * time.Second,
name: "only_errors",
leaseDurationSeconds: 15,
incrementSeconds: 15,
renew: func(_ string, _ int) (*Secret, error) {
return nil, fmt.Errorf("renew failure")
},
expectError: nil,
expectRenewal: false,
},
{
maxTestTime: 15 * time.Second,
name: "negative_lease_duration",
leaseDurationSeconds: -15,
incrementSeconds: 15,
renew: func(_ string, _ int) (*Secret, error) {
return renewedSecret, nil
},
expectError: nil,
expectRenewal: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
v, err := client.NewLifetimeWatcher(&LifetimeWatcherInput{
Secret: &Secret{
LeaseDuration: tc.leaseDurationSeconds,
},
Increment: tc.incrementSeconds,
})
if err != nil {
t.Fatal(err)
}
doneCh := make(chan error, 1)
go func() {
doneCh <- v.doRenewWithOptions(false, false,
tc.leaseDurationSeconds, "myleaseID", tc.renew, time.Second)
}()
defer v.Stop()
receivedRenewal := false
receivedDone := false
ChannelLoop:
for {
select {
case <-time.After(tc.maxTestTime):
t.Fatalf("renewal didn't happen")
case r := <-v.RenewCh():
if !tc.expectRenewal {
t.Fatal("expected no renewals")
}
if r.Secret != renewedSecret {
t.Fatalf("expected secret %v, got %v", renewedSecret, r.Secret)
}
receivedRenewal = true
if !receivedDone {
continue ChannelLoop
}
break ChannelLoop
case err := <-doneCh:
receivedDone = true
if tc.expectError != nil && !errors.Is(err, tc.expectError) {
t.Fatalf("expected error %q, got: %v", tc.expectError, err)
}
if tc.expectError == nil && err != nil {
t.Fatalf("expected no error, got: %v", err)
}
if tc.expectRenewal && !receivedRenewal {
// We might have received the stop before the renew call on the channel.
continue ChannelLoop
}
break ChannelLoop
}
}
if tc.expectRenewal && !receivedRenewal {
t.Fatalf("expected at least one renewal, got none.")
}
})
}
}
// TestCalcSleepPeriod uses property based testing to evaluate the calculateSleepDuration
// function of LifeTimeWatchers, but also incidentally tests "calculateGrace".
// This is on account of "calculateSleepDuration" performing the "calculateGrace"
// function in particular instances.
// Both of these functions support the vital functionality of the LifeTimeWatcher
// and therefore should be tested rigorously.
func TestCalcSleepPeriod(t *testing.T) {
c := quick.Config{
MaxCount: 10000,
Values: func(values []reflect.Value, r *rand.Rand) {
leaseDuration := r.Int63()
priorDuration := r.Int63n(leaseDuration)
remainingLeaseDuration := r.Int63n(priorDuration)
increment := r.Int63n(remainingLeaseDuration)
values[0] = reflect.ValueOf(r)
values[1] = reflect.ValueOf(time.Duration(leaseDuration))
values[2] = reflect.ValueOf(time.Duration(priorDuration))
values[3] = reflect.ValueOf(time.Duration(remainingLeaseDuration))
values[4] = reflect.ValueOf(time.Duration(increment))
},
}
// tests that "calculateSleepDuration" will always return a value less than
// the remaining lease duration given a random leaseDuration, priorDuration, remainingLeaseDuration, and increment.
// Inputs are generated so that:
// leaseDuration > priorDuration > remainingLeaseDuration
// and remainingLeaseDuration > increment
if err := quick.Check(func(r *rand.Rand, leaseDuration, priorDuration, remainingLeaseDuration, increment time.Duration) bool {
lw := LifetimeWatcher{
grace: 0,
increment: int(increment.Seconds()),
random: r,
}
lw.calculateGrace(remainingLeaseDuration, increment)
// ensure that we sleep for less than the remaining lease.
return lw.calculateSleepDuration(remainingLeaseDuration, priorDuration) < remainingLeaseDuration
}, &c); err != nil {
t.Error(err)
}
}

View File

@ -1,44 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"strings"
"testing"
)
func TestRequestSetJSONBody(t *testing.T) {
var r Request
raw := map[string]interface{}{"foo": "bar"}
if err := r.SetJSONBody(raw); err != nil {
t.Fatalf("err: %s", err)
}
expected := `{"foo":"bar"}`
actual := strings.TrimSpace(string(r.BodyBytes))
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
func TestRequestResetJSONBody(t *testing.T) {
var r Request
raw := map[string]interface{}{"foo": "bar"}
if err := r.SetJSONBody(raw); err != nil {
t.Fatalf("err: %s", err)
}
if err := r.ResetJSONBody(); err != nil {
t.Fatalf("err: %s", err)
}
buf := make([]byte, len(r.BodyBytes))
copy(buf, r.BodyBytes)
expected := `{"foo":"bar"}`
actual := strings.TrimSpace(string(buf))
if actual != expected {
t.Fatalf("bad: actual %s, expected %s", actual, expected)
}
}

View File

@ -1,211 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"testing"
)
func TestTokenPolicies(t *testing.T) {
var s *Secret
// Verify some of the short-circuit paths in the function
if policies, err := s.TokenPolicies(); policies != nil {
t.Errorf("policies was not nil, got %v", policies)
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
}
s = &Secret{}
if policies, err := s.TokenPolicies(); policies != nil {
t.Errorf("policies was not nil, got %v", policies)
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
}
s.Auth = &SecretAuth{}
if policies, err := s.TokenPolicies(); policies != nil {
t.Errorf("policies was not nil, got %v", policies)
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
}
s.Auth.Policies = []string{}
if policies, err := s.TokenPolicies(); policies != nil {
t.Errorf("policies was not nil, got %v", policies)
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
}
s.Auth.Policies = []string{"test"}
if policies, err := s.TokenPolicies(); policies == nil {
t.Error("policies was nil")
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
}
s.Auth = nil
s.Data = make(map[string]interface{})
if policies, err := s.TokenPolicies(); policies != nil {
t.Errorf("policies was not nil, got %v", policies)
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
}
// Verify that s.Data["policies"] are properly processed
{
policyList := make([]string, 0)
s.Data["policies"] = policyList
if policies, err := s.TokenPolicies(); len(policies) != len(policyList) {
t.Errorf("expecting policies length %d, got %d", len(policyList), len(policies))
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
} else if s.Auth == nil {
t.Error("Auth field is still nil")
}
policyList = append(policyList, "policy1", "policy2")
s.Data["policies"] = policyList
if policies, err := s.TokenPolicies(); len(policyList) != 2 {
t.Errorf("expecting policies length %d, got %d", len(policyList), len(policies))
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
} else if s.Auth == nil {
t.Error("Auth field is still nil")
}
}
// Do it again but with an interface{} slice
{
s.Auth = nil
policyList := make([]interface{}, 0)
s.Data["policies"] = policyList
if policies, err := s.TokenPolicies(); len(policies) != len(policyList) {
t.Errorf("expecting policies length %d, got %d", len(policyList), len(policies))
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
} else if s.Auth == nil {
t.Error("Auth field is still nil")
}
policyItems := make([]interface{}, 2)
policyItems[0] = "policy1"
policyItems[1] = "policy2"
policyList = append(policyList, policyItems...)
s.Data["policies"] = policyList
if policies, err := s.TokenPolicies(); len(policies) != 2 {
t.Errorf("expecting policies length %d, got %d", len(policyList), len(policies))
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
} else if s.Auth == nil {
t.Error("Auth field is still nil")
}
s.Auth = nil
s.Data["policies"] = 7.0
if policies, err := s.TokenPolicies(); err == nil {
t.Error("err was nil")
} else if policies != nil {
t.Errorf("policies was not nil, got %v", policies)
}
s.Auth = nil
s.Data["policies"] = []int{2, 3, 5, 8, 13}
if policies, err := s.TokenPolicies(); err == nil {
t.Error("err was nil")
} else if policies != nil {
t.Errorf("policies was not nil, got %v", policies)
}
}
s.Auth = nil
s.Data["policies"] = nil
if policies, err := s.TokenPolicies(); err != nil {
t.Errorf("err was not nil, got %v", err)
} else if policies != nil {
t.Errorf("policies was not nil, got %v", policies)
}
// Verify that logic that merges s.Data["policies"] and s.Data["identity_policies"] works
{
policyList := []string{"policy1", "policy2", "policy3"}
s.Data["policies"] = policyList[:1]
s.Data["identity_policies"] = "not_a_slice"
s.Auth = nil
if policies, err := s.TokenPolicies(); err == nil {
t.Error("err was nil")
} else if policies != nil {
t.Errorf("policies was not nil, got %v", policies)
}
s.Data["identity_policies"] = policyList[1:]
if policies, err := s.TokenPolicies(); len(policyList) != len(policies) {
t.Errorf("expecting policies length %d, got %d", len(policyList), len(policies))
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
} else if s.Auth == nil {
t.Error("Auth field is still nil")
}
}
// Do it again but with an interface{} slice
{
policyList := []interface{}{"policy1", "policy2", "policy3"}
s.Data["policies"] = policyList[:1]
s.Data["identity_policies"] = "not_a_slice"
s.Auth = nil
if policies, err := s.TokenPolicies(); err == nil {
t.Error("err was nil")
} else if policies != nil {
t.Errorf("policies was not nil, got %v", policies)
}
s.Data["identity_policies"] = policyList[1:]
if policies, err := s.TokenPolicies(); len(policyList) != len(policies) {
t.Errorf("expecting policies length %d, got %d", len(policyList), len(policies))
} else if err != nil {
t.Errorf("err was not nil, got %v", err)
} else if s.Auth == nil {
t.Error("Auth field is still nil")
}
s.Auth = nil
s.Data["identity_policies"] = []int{2, 3, 5, 8, 13}
if policies, err := s.TokenPolicies(); err == nil {
t.Error("err was nil")
} else if policies != nil {
t.Errorf("policies was not nil, got %v", policies)
}
}
s.Auth = nil
s.Data["policies"] = []string{"policy1"}
s.Data["identity_policies"] = nil
if policies, err := s.TokenPolicies(); err != nil {
t.Errorf("err was not nil, got %v", err)
} else if len(policies) != 1 {
t.Errorf("expecting policies length %d, got %d", 1, len(policies))
} else if s.Auth == nil {
t.Error("Auth field is still nil")
}
}

View File

@ -1,112 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"fmt"
"net/http"
"strings"
"testing"
)
func TestSSH_CreateTLSClient(t *testing.T) {
// load the default configuration
config, err := LoadSSHHelperConfig("./test-fixtures/agent_config.hcl")
if err != nil {
panic(fmt.Sprintf("error loading agent's config file: %s", err))
}
client, err := config.NewClient()
if err != nil {
panic(fmt.Sprintf("error creating the client: %s", err))
}
// Provide a certificate and enforce setting of transport
config.CACert = "./test-fixtures/vault.crt"
client, err = config.NewClient()
if err != nil {
panic(fmt.Sprintf("error creating the client: %s", err))
}
if client.config.HttpClient.Transport == nil {
panic(fmt.Sprintf("error creating client with TLS transport"))
}
}
func TestSSH_CreateTLSClient_tlsServerName(t *testing.T) {
// Ensure that the HTTP client is associated with the configured TLS server name.
tlsServerName := "tls.server.name"
config, err := ParseSSHHelperConfig(fmt.Sprintf(`
vault_addr = "1.2.3.4"
tls_server_name = "%s"
`, tlsServerName))
if err != nil {
panic(fmt.Sprintf("error loading config: %s", err))
}
client, err := config.NewClient()
if err != nil {
panic(fmt.Sprintf("error creating the client: %s", err))
}
actualTLSServerName := client.config.HttpClient.Transport.(*http.Transport).TLSClientConfig.ServerName
if actualTLSServerName != tlsServerName {
panic(fmt.Sprintf("incorrect TLS server name. expected: %s actual: %s", tlsServerName, actualTLSServerName))
}
}
func TestParseSSHHelperConfig(t *testing.T) {
config, err := ParseSSHHelperConfig(`
vault_addr = "1.2.3.4"
`)
if err != nil {
t.Fatal(err)
}
if config.SSHMountPoint != SSHHelperDefaultMountPoint {
t.Errorf("expected %q to be %q", config.SSHMountPoint, SSHHelperDefaultMountPoint)
}
}
func TestParseSSHHelperConfig_missingVaultAddr(t *testing.T) {
_, err := ParseSSHHelperConfig("")
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), `missing config "vault_addr"`) {
t.Errorf("bad error: %s", err)
}
}
func TestParseSSHHelperConfig_badKeys(t *testing.T) {
_, err := ParseSSHHelperConfig(`
vault_addr = "1.2.3.4"
nope = "bad"
`)
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), `ssh_helper: invalid key "nope" on line 3`) {
t.Errorf("bad error: %s", err)
}
}
func TestParseSSHHelperConfig_tlsServerName(t *testing.T) {
tlsServerName := "tls.server.name"
config, err := ParseSSHHelperConfig(fmt.Sprintf(`
vault_addr = "1.2.3.4"
tls_server_name = "%s"
`, tlsServerName))
if err != nil {
t.Fatal(err)
}
if config.TLSServerName != tlsServerName {
t.Errorf("incorrect TLS server name. expected: %s actual: %s", tlsServerName, config.TLSServerName)
}
}

View File

@ -1,152 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestListMounts(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultMountsHandler))
defer mockVaultServer.Close()
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
if err != nil {
t.Fatal(err)
}
resp, err := client.Sys().ListMounts()
if err != nil {
t.Fatal(err)
}
expectedMounts := map[string]struct {
Type string
Version string
}{
"cubbyhole/": {Type: "cubbyhole", Version: "v1.0.0"},
"identity/": {Type: "identity", Version: ""},
"secret/": {Type: "kv", Version: ""},
"sys/": {Type: "system", Version: ""},
}
for path, mount := range resp {
expected, ok := expectedMounts[path]
if !ok {
t.Errorf("Unexpected mount: %s: %+v", path, mount)
continue
}
if expected.Type != mount.Type || expected.Version != mount.PluginVersion {
t.Errorf("Mount did not match: %s -> expected %+v but got %+v", path, expected, mount)
}
}
for path, expected := range expectedMounts {
mount, ok := resp[path]
if !ok {
t.Errorf("Expected mount not found mount: %s: %+v", path, expected)
continue
}
if expected.Type != mount.Type || expected.Version != mount.PluginVersion {
t.Errorf("Mount did not match: %s -> expected %+v but got %+v", path, expected, mount)
}
}
}
func mockVaultMountsHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(listMountsResponse))
}
const listMountsResponse = `{
"request_id": "3cd881e9-ea50-2e06-90b2-5641667485fa",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"cubbyhole/": {
"accessor": "cubbyhole_2e3fc28d",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0
},
"description": "per-token private secret storage",
"external_entropy_access": false,
"local": true,
"options": null,
"plugin_version": "v1.0.0",
"running_sha256": "",
"running_plugin_version": "",
"seal_wrap": false,
"type": "cubbyhole",
"uuid": "575063dc-5ef8-4487-c842-22c494c19a6f"
},
"identity/": {
"accessor": "identity_6e01c327",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0,
"passthrough_request_headers": [
"Authorization"
]
},
"description": "identity store",
"external_entropy_access": false,
"local": false,
"options": null,
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": "",
"seal_wrap": false,
"type": "identity",
"uuid": "187d7eba-3471-554b-c2d9-1479612c8046"
},
"secret/": {
"accessor": "kv_3e2f282f",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0
},
"description": "key/value secret storage",
"external_entropy_access": false,
"local": false,
"options": {
"version": "2"
},
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": "",
"seal_wrap": false,
"type": "kv",
"uuid": "13375e0f-876e-7e96-0a3e-076f37b6b69d"
},
"sys/": {
"accessor": "system_93503264",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0,
"passthrough_request_headers": [
"Accept"
]
},
"description": "system endpoints used for control, policy and debugging",
"external_entropy_access": false,
"local": false,
"options": null,
"plugin_version": "",
"running_sha256": "",
"running_plugin_version": "",
"seal_wrap": true,
"type": "system",
"uuid": "1373242d-cc4d-c023-410b-7f336e7ba0a8"
}
}
}`

View File

@ -1,324 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"context"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/hashicorp/go-secure-stdlib/strutil"
)
func TestRegisterPlugin(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerRegister))
defer mockVaultServer.Close()
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
if err != nil {
t.Fatal(err)
}
err = client.Sys().RegisterPluginWithContext(context.Background(), &RegisterPluginInput{
Version: "v1.0.0",
})
if err != nil {
t.Fatal(err)
}
}
func TestListPlugins(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerList))
defer mockVaultServer.Close()
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
if err != nil {
t.Fatal(err)
}
for name, tc := range map[string]struct {
input ListPluginsInput
expectedPlugins map[PluginType][]string
}{
"no type specified": {
input: ListPluginsInput{},
expectedPlugins: map[PluginType][]string{
PluginTypeCredential: {"alicloud"},
PluginTypeDatabase: {"cassandra-database-plugin"},
PluginTypeSecrets: {"ad", "alicloud"},
},
},
"only auth plugins": {
input: ListPluginsInput{Type: PluginTypeCredential},
expectedPlugins: map[PluginType][]string{
PluginTypeCredential: {"alicloud"},
},
},
"only database plugins": {
input: ListPluginsInput{Type: PluginTypeDatabase},
expectedPlugins: map[PluginType][]string{
PluginTypeDatabase: {"cassandra-database-plugin"},
},
},
"only secret plugins": {
input: ListPluginsInput{Type: PluginTypeSecrets},
expectedPlugins: map[PluginType][]string{
PluginTypeSecrets: {"ad", "alicloud"},
},
},
} {
t.Run(name, func(t *testing.T) {
resp, err := client.Sys().ListPluginsWithContext(context.Background(), &tc.input)
if err != nil {
t.Fatal(err)
}
for pluginType, expected := range tc.expectedPlugins {
actualPlugins := resp.PluginsByType[pluginType]
if len(expected) != len(actualPlugins) {
t.Fatal("Wrong number of plugins", expected, actualPlugins)
}
for i := range actualPlugins {
if expected[i] != actualPlugins[i] {
t.Fatalf("Expected %q but got %q", expected[i], actualPlugins[i])
}
}
for _, expectedPlugin := range expected {
found := false
for _, plugin := range resp.Details {
if plugin.Type == pluginType.String() && plugin.Name == expectedPlugin {
found = true
break
}
}
if !found {
t.Errorf("Expected to find %s plugin %s but not found in details: %#v", pluginType.String(), expectedPlugin, resp.Details)
}
}
}
for _, actual := range resp.Details {
pluginType, err := ParsePluginType(actual.Type)
if err != nil {
t.Fatal(err)
}
if !strutil.StrListContains(tc.expectedPlugins[pluginType], actual.Name) {
t.Errorf("Did not expect to find %s in details", actual.Name)
}
}
})
}
}
func TestGetPlugin(t *testing.T) {
for name, tc := range map[string]struct {
version string
body string
expected GetPluginResponse
}{
"builtin": {
body: getResponse,
expected: GetPluginResponse{
Args: nil,
Builtin: true,
Command: "",
Name: "azure",
SHA256: "",
DeprecationStatus: "supported",
Version: "v0.14.0+builtin",
},
},
"external": {
version: "v1.0.0",
body: getResponseExternal,
expected: GetPluginResponse{
Args: []string{},
Builtin: false,
Command: "azure-plugin",
Name: "azure",
SHA256: "8ba442dba253803685b05e35ad29dcdebc48dec16774614aa7a4ebe53c1e90e1",
DeprecationStatus: "",
Version: "v1.0.0",
},
},
"old server": {
body: getResponseOldServerVersion,
expected: GetPluginResponse{
Args: nil,
Builtin: true,
Command: "",
Name: "azure",
SHA256: "",
DeprecationStatus: "",
Version: "",
},
},
} {
t.Run(name, func(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerInfo(tc.body)))
defer mockVaultServer.Close()
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
if err != nil {
t.Fatal(err)
}
input := GetPluginInput{
Name: "azure",
Type: PluginTypeSecrets,
}
if tc.version != "" {
input.Version = tc.version
}
info, err := client.Sys().GetPluginWithContext(context.Background(), &input)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tc.expected, *info) {
t.Errorf("expected: %#v\ngot: %#v", tc.expected, info)
}
})
}
}
func mockVaultHandlerInfo(body string) func(w http.ResponseWriter, _ *http.Request) {
return func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(body))
}
}
const getResponse = `{
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c495241",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"args": null,
"builtin": true,
"command": "",
"deprecation_status": "supported",
"name": "azure",
"sha256": "",
"version": "v0.14.0+builtin"
},
"wrap_info": null,
"warnings": null,
"auth": null
}`
const getResponseExternal = `{
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c495241",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"args": [],
"builtin": false,
"command": "azure-plugin",
"name": "azure",
"sha256": "8ba442dba253803685b05e35ad29dcdebc48dec16774614aa7a4ebe53c1e90e1",
"version": "v1.0.0"
},
"wrap_info": null,
"warnings": null,
"auth": null
}`
const getResponseOldServerVersion = `{
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c495241",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"args": null,
"builtin": true,
"command": "",
"name": "azure",
"sha256": ""
},
"wrap_info": null,
"warnings": null,
"auth": null
}`
func mockVaultHandlerList(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(listUntypedResponse))
}
const listUntypedResponse = `{
"request_id": "82601a91-cd7a-718f-feca-f573449cc1bb",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"auth": [
"alicloud"
],
"database": [
"cassandra-database-plugin"
],
"secret": [
"ad",
"alicloud"
],
"some_other_unexpected_key": [
{
"objectKey": "objectValue"
},
{
"arbitraryData": 7
}
],
"detailed": [
{
"type": "auth",
"name": "alicloud",
"version": "v0.13.0+builtin",
"builtin": true,
"deprecation_status": "supported"
},
{
"type": "database",
"name": "cassandra-database-plugin",
"version": "v1.13.0+builtin.vault",
"builtin": true,
"deprecation_status": "supported"
},
{
"type": "secret",
"name": "ad",
"version": "v0.14.0+builtin",
"builtin": true,
"deprecation_status": "supported"
},
{
"type": "secret",
"name": "alicloud",
"version": "v0.13.0+builtin",
"builtin": true,
"deprecation_status": "supported"
}
]
},
"wrap_info": null,
"warnings": null,
"auth": null
}`
func mockVaultHandlerRegister(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(registerResponse))
}
const registerResponse = `{}`

View File

@ -1,5 +0,0 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
vault_addr="http://127.0.0.1:8200"
ssh_mount_point="ssh"

View File

@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIUf+jhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw
MTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS
TRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGn
SgMld6ZWRhNheZhA6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmi
YYMiIWplidMmMO5NTRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5
donyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVG
B+5+AAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABo4H1
MIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm++e
HpyM3p708bgZJuRYEdX1o+UwHwYDVR0jBBgwFoAUncSzT/6HMexyuiU9/7EgHu+o
k5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x
OjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A
AAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br
aS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy+SgMIrwfs
X1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4
aYqNKFWrRaBRAaaYZ/O1ApRTOrXqRx9Eqr0H1BXLsoAq+mWassL8sf6siae+CpwA
KqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU+idkuqfV2h1BQKgSEhFDABjFdTCN
QDAHsEHsi2M4/jRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj
xqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc/J9DIQM+Xmk=
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxSTRAVnygAftetT8pu
HflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGnSgMld6ZWRhNheZhA
6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmiYYMiIWplidMmMO5N
TRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5donyqtnaHuIJGuUd
y54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVGB+5+AAGF5iuHC3N2
DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABAoIBAHR7fFV0eAGaopsX
9OD0TUGlsephBXb43g0GYHfJ/1Ew18w9oaxszJEqkl+PB4W3xZ3yG3e8ZomxDOhF
RreF2WgG5xOfhDogMwu6NodbArfgnAvoC6JnW3qha8HMP4F500RFVyCRcd6A3Frd
rFtaZn/UyCsBAN8/zkwPeYHayo7xX6d9kzgRl9HluEX5PXI5+3uiBDUiM085gkLI
5Cmadh9fMdjfhDXI4x2JYmILpp/9Nlc/krB15s5n1MPNtn3yL0TI0tWp0WlwDCV7
oUm1SfIM0F1fXGFyFDcqwoIr6JCQgXk6XtTg31YhH1xgUIclUVdtHqmAwAbLdIhQ
GAiHn2kCgYEAwD4pZ8HfpiOG/EHNoWsMATc/5yC7O8F9WbvcHZQIymLY4v/7HKZb
VyOR6UQ5/O2cztSGIuKSF6+OK1C34lOyCuTSOTFrjlgEYtLIXjdGLfFdtOO8GRQR
akVXdwuzNAjTBaH5eXbG+NKcjmCvZL48dQVlfDTVulzFGbcsVTHIMQUCgYEA7IQI
FVsKnY3KqpyGqXq92LMcsT3XgW6X1BIIV+YhJ5AFUFkFrjrbXs94/8XyLfi0xBQy
efK+8g5sMs7koF8LyZEcAXWZJQduaKB71hoLlRaU4VQkL/dl2B6VFmAII/CsRCYh
r9RmDN2PF/mp98Ih9dpC1VqcCDRGoTYsd7jLalMCgYAMgH5k1wDaZxkSMp1S0AlZ
0uP+/evvOOgT+9mWutfPgZolOQx1koQCKLgGeX9j6Xf3I28NubpSfAI84uTyfQrp
FnRtb79U5Hh0jMynA+U2e6niZ6UF5H41cQj9Hu+qhKBkj2IP+h96cwfnYnZFkPGR
kqZE65KyqfHPeFATwkcImQKBgCdrfhlpGiTWXCABhKQ8s+WpPLAB2ahV8XJEKyXT
UlVQuMIChGLcpnFv7P/cUxf8asx/fUY8Aj0/0CLLvulHziQjTmKj4gl86pb/oIQ3
xRRtNhU0O+/OsSfLORgIm3K6C0w0esregL/GMbJSR1TnA1gBr7/1oSnw5JC8Ab9W
injHAoGAJT1MGAiQrhlt9GCGe6Ajw4omdbY0wS9NXefnFhf7EwL0es52ezZ28zpU
2LXqSFbtann5CHgpSLxiMYPDIf+er4xgg9Bz34tz1if1rDfP2Qrxdrpr4jDnrGT3
gYC2qCpvVD9RRUMKFfnJTfl5gMQdBW/LINkHtJ82snAeLl3gjQ4=
-----END RSA PRIVATE KEY-----

View File

@ -1,74 +0,0 @@
Key Value
lease_id pki/issue/example-dot-com/d8214077-9976-8c68-9c07-6610da30aea4
lease_duration 279359999
lease_renewable false
certificate -----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIUf+jhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw
MTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS
TRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGn
SgMld6ZWRhNheZhA6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmi
YYMiIWplidMmMO5NTRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5
donyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVG
B+5+AAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABo4H1
MIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm++e
HpyM3p708bgZJuRYEdX1o+UwHwYDVR0jBBgwFoAUncSzT/6HMexyuiU9/7EgHu+o
k5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x
OjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A
AAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br
aS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy+SgMIrwfs
X1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4
aYqNKFWrRaBRAaaYZ/O1ApRTOrXqRx9Eqr0H1BXLsoAq+mWassL8sf6siae+CpwA
KqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU+idkuqfV2h1BQKgSEhFDABjFdTCN
QDAHsEHsi2M4/jRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj
xqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc/J9DIQM+Xmk=
-----END CERTIFICATE-----
issuing_ca -----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----
private_key -----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxSTRAVnygAftetT8pu
HflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGnSgMld6ZWRhNheZhA
6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmiYYMiIWplidMmMO5N
TRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5donyqtnaHuIJGuUd
y54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVGB+5+AAGF5iuHC3N2
DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABAoIBAHR7fFV0eAGaopsX
9OD0TUGlsephBXb43g0GYHfJ/1Ew18w9oaxszJEqkl+PB4W3xZ3yG3e8ZomxDOhF
RreF2WgG5xOfhDogMwu6NodbArfgnAvoC6JnW3qha8HMP4F500RFVyCRcd6A3Frd
rFtaZn/UyCsBAN8/zkwPeYHayo7xX6d9kzgRl9HluEX5PXI5+3uiBDUiM085gkLI
5Cmadh9fMdjfhDXI4x2JYmILpp/9Nlc/krB15s5n1MPNtn3yL0TI0tWp0WlwDCV7
oUm1SfIM0F1fXGFyFDcqwoIr6JCQgXk6XtTg31YhH1xgUIclUVdtHqmAwAbLdIhQ
GAiHn2kCgYEAwD4pZ8HfpiOG/EHNoWsMATc/5yC7O8F9WbvcHZQIymLY4v/7HKZb
VyOR6UQ5/O2cztSGIuKSF6+OK1C34lOyCuTSOTFrjlgEYtLIXjdGLfFdtOO8GRQR
akVXdwuzNAjTBaH5eXbG+NKcjmCvZL48dQVlfDTVulzFGbcsVTHIMQUCgYEA7IQI
FVsKnY3KqpyGqXq92LMcsT3XgW6X1BIIV+YhJ5AFUFkFrjrbXs94/8XyLfi0xBQy
efK+8g5sMs7koF8LyZEcAXWZJQduaKB71hoLlRaU4VQkL/dl2B6VFmAII/CsRCYh
r9RmDN2PF/mp98Ih9dpC1VqcCDRGoTYsd7jLalMCgYAMgH5k1wDaZxkSMp1S0AlZ
0uP+/evvOOgT+9mWutfPgZolOQx1koQCKLgGeX9j6Xf3I28NubpSfAI84uTyfQrp
FnRtb79U5Hh0jMynA+U2e6niZ6UF5H41cQj9Hu+qhKBkj2IP+h96cwfnYnZFkPGR
kqZE65KyqfHPeFATwkcImQKBgCdrfhlpGiTWXCABhKQ8s+WpPLAB2ahV8XJEKyXT
UlVQuMIChGLcpnFv7P/cUxf8asx/fUY8Aj0/0CLLvulHziQjTmKj4gl86pb/oIQ3
xRRtNhU0O+/OsSfLORgIm3K6C0w0esregL/GMbJSR1TnA1gBr7/1oSnw5JC8Ab9W
injHAoGAJT1MGAiQrhlt9GCGe6Ajw4omdbY0wS9NXefnFhf7EwL0es52ezZ28zpU
2LXqSFbtann5CHgpSLxiMYPDIf+er4xgg9Bz34tz1if1rDfP2Qrxdrpr4jDnrGT3
gYC2qCpvVD9RRUMKFfnJTfl5gMQdBW/LINkHtJ82snAeLl3gjQ4=
-----END RSA PRIVATE KEY-----
private_key_type rsa

View File

@ -1,74 +0,0 @@
Key Value
lease_id pki/root/generate/exported/7bf99d76-dd3e-2c5b-04ce-5253062ad586
lease_duration 315359999
lease_renewable false
certificate -----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----
expiration 1.772072879e+09
issuing_ca -----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----
private_key -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA7FMy+FO4hFzZiHFmmY/B6A/zpyCep9PWZfGLUDtDuprHbg2p
t/TQjegMxC0AmWPZEHeG9FIJvT+WQXuLUG5v5MbG4zs21mqnYXwWAbTPaZ35bpp3
BbbquXFtAqOZG8yfrob1g9OSgmY+bG3ZNxxv35qmbBbuWyUvmPwfjzEAclMxv48w
/2Rs4dXkBuvc9PiNdQ9Sv+ZYG8GIqIcbRd38cSaXI4Q94BOHEr4jm1vqb54H7twv
0Gy9CnLXfn7ZtEDFSmG8WLk2GvIna+UC+gnxSqKCw0rcTby63rQUCgdJZF8VQUVi
18+BMNLXI4pT/P9cxTaCJC/YeuV5a3SaniOoFQIDAQABAoIBAQCoGZJC84JnnIgb
ttZNWuWKBXbCJcDVDikOQJ9hBZbqsFg1X0CfGmQS3MHf9Ubc1Ro8zVjQh15oIEfn
8lIpdzTeXcpxLdiW8ix3ekVJF20F6pnXY8ZP6UnTeOwamXY6QPZAtb0D9UXcvY+f
nw+IVRD6082XS0Rmzu+peYWVXDy+FDN+HJRANBcdJZz8gOmNBIe0qDWx1b85d/s8
2Kk1Wwdss1IwAGeSddTSwzBNaaHdItZaMZOqPW1gRyBfVSkcUQIE6zn2RKw2b70t
grkIvyRcTdfmiKbqkkJ+eR+ITOUt0cBZSH4cDjlQA+r7hulvoBpQBRj068Toxkcc
bTagHaPBAoGBAPWPGVkHqhTbJ/DjmqDIStxby2M1fhhHt4xUGHinhUYjQjGOtDQ9
0mfaB7HObudRiSLydRAVGAHGyNJdQcTeFxeQbovwGiYKfZSA1IGpea7dTxPpGEdN
ksA0pzSp9MfKzX/MdLuAkEtO58aAg5YzsgX9hDNxo4MhH/gremZhEGZlAoGBAPZf
lqdYvAL0fjHGJ1FUEalhzGCGE9PH2iOqsxqLCXK7bDbzYSjvuiHkhYJHAOgVdiW1
lB34UHHYAqZ1VVoFqJ05gax6DE2+r7K5VV3FUCaC0Zm3pavxchU9R/TKP82xRrBj
AFWwdgDTxUyvQEmgPR9sqorftO71Iz2tiwyTpIfxAoGBAIhEMLzHFAse0rtKkrRG
ccR27BbRyHeQ1Lp6sFnEHKEfT8xQdI/I/snCpCJ3e/PBu2g5Q9z416mktiyGs8ib
thTNgYsGYnxZtfaCx2pssanoBcn2wBJRae5fSapf5gY49HDG9MBYR7qCvvvYtSzU
4yWP2ZzyotpRt3vwJKxLkN5BAoGAORHpZvhiDNkvxj3da7Rqpu7VleJZA2y+9hYb
iOF+HcqWhaAY+I+XcTRrTMM/zYLzLEcEeXDEyao86uwxCjpXVZw1kotvAC9UqbTO
tnr3VwRkoxPsV4kFYTAh0+1pnC8dbcxxDmhi3Uww3tOVs7hfkEDuvF6XnebA9A+Y
LyCgMzECgYEA6cCU8QODOivIKWFRXucvWckgE6MYDBaAwe6qcLsd1Q/gpE2e3yQc
4RB3bcyiPROLzMLlXFxf1vSNJQdIaVfrRv+zJeGIiivLPU8+Eq4Lrb+tl1LepcOX
OzQeADTSCn5VidOfjDkIst9UXjMlrFfV9/oJEw5Eiqa6lkNPCGDhfA8=
-----END RSA PRIVATE KEY-----
private_key_type rsa
serial_number 6f:98:9d:f8:67:1a:31:e3:27:60:1b:f7:32:f7:53:19:68:a0:c8:9d

View File

@ -1,12 +0,0 @@
-----BEGIN X509 CRL-----
MIIBrjCBlzANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbRcN
MTYwMjI5MDIyOTE3WhcNMjUwMTA1MTAyOTE3WjArMCkCFG+YnfhnGjHjJ2Ab9zL3
UxlooMidFxExNjAyMjgyMTI5MTctMDUwMKAjMCEwHwYDVR0jBBgwFoAUncSzT/6H
MexyuiU9/7EgHu+ok5swDQYJKoZIhvcNAQELBQADggEBAG9YDXpNe4LJroKZmVCn
HqMhW8eyzyaPak2nPPGCVUnc6vt8rlBYQU+xlBizD6xatZQDMPgrT8sBl9W3ysXk
RUlliHsT/SHddMz5dAZsBPRMJ7pYWLTx8jI4w2WRfbSyI4bY/6qTRNkEBUv+Fk8J
xvwB89+EM0ENcVMhv9ghsUA8h7kOg673HKwRstLDAzxS/uLmEzFjj8SV2m5DbV2Y
UUCKRSV20/kxJMIC9x2KikZhwOSyv1UE1otD+RQvbfAoZPUDmvp2FR/E0NGjBBOg
1TtCPRrl63cjqU3s8KQ4uah9Vj+Cwcu9n/yIKKtNQq4NKHvagv8GlUsoJ4BdAxCw
IA0=
-----END X509 CRL-----

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA7FMy+FO4hFzZiHFmmY/B6A/zpyCep9PWZfGLUDtDuprHbg2p
t/TQjegMxC0AmWPZEHeG9FIJvT+WQXuLUG5v5MbG4zs21mqnYXwWAbTPaZ35bpp3
BbbquXFtAqOZG8yfrob1g9OSgmY+bG3ZNxxv35qmbBbuWyUvmPwfjzEAclMxv48w
/2Rs4dXkBuvc9PiNdQ9Sv+ZYG8GIqIcbRd38cSaXI4Q94BOHEr4jm1vqb54H7twv
0Gy9CnLXfn7ZtEDFSmG8WLk2GvIna+UC+gnxSqKCw0rcTby63rQUCgdJZF8VQUVi
18+BMNLXI4pT/P9cxTaCJC/YeuV5a3SaniOoFQIDAQABAoIBAQCoGZJC84JnnIgb
ttZNWuWKBXbCJcDVDikOQJ9hBZbqsFg1X0CfGmQS3MHf9Ubc1Ro8zVjQh15oIEfn
8lIpdzTeXcpxLdiW8ix3ekVJF20F6pnXY8ZP6UnTeOwamXY6QPZAtb0D9UXcvY+f
nw+IVRD6082XS0Rmzu+peYWVXDy+FDN+HJRANBcdJZz8gOmNBIe0qDWx1b85d/s8
2Kk1Wwdss1IwAGeSddTSwzBNaaHdItZaMZOqPW1gRyBfVSkcUQIE6zn2RKw2b70t
grkIvyRcTdfmiKbqkkJ+eR+ITOUt0cBZSH4cDjlQA+r7hulvoBpQBRj068Toxkcc
bTagHaPBAoGBAPWPGVkHqhTbJ/DjmqDIStxby2M1fhhHt4xUGHinhUYjQjGOtDQ9
0mfaB7HObudRiSLydRAVGAHGyNJdQcTeFxeQbovwGiYKfZSA1IGpea7dTxPpGEdN
ksA0pzSp9MfKzX/MdLuAkEtO58aAg5YzsgX9hDNxo4MhH/gremZhEGZlAoGBAPZf
lqdYvAL0fjHGJ1FUEalhzGCGE9PH2iOqsxqLCXK7bDbzYSjvuiHkhYJHAOgVdiW1
lB34UHHYAqZ1VVoFqJ05gax6DE2+r7K5VV3FUCaC0Zm3pavxchU9R/TKP82xRrBj
AFWwdgDTxUyvQEmgPR9sqorftO71Iz2tiwyTpIfxAoGBAIhEMLzHFAse0rtKkrRG
ccR27BbRyHeQ1Lp6sFnEHKEfT8xQdI/I/snCpCJ3e/PBu2g5Q9z416mktiyGs8ib
thTNgYsGYnxZtfaCx2pssanoBcn2wBJRae5fSapf5gY49HDG9MBYR7qCvvvYtSzU
4yWP2ZzyotpRt3vwJKxLkN5BAoGAORHpZvhiDNkvxj3da7Rqpu7VleJZA2y+9hYb
iOF+HcqWhaAY+I+XcTRrTMM/zYLzLEcEeXDEyao86uwxCjpXVZw1kotvAC9UqbTO
tnr3VwRkoxPsV4kFYTAh0+1pnC8dbcxxDmhi3Uww3tOVs7hfkEDuvF6XnebA9A+Y
LyCgMzECgYEA6cCU8QODOivIKWFRXucvWckgE6MYDBaAwe6qcLsd1Q/gpE2e3yQc
4RB3bcyiPROLzMLlXFxf1vSNJQdIaVfrRv+zJeGIiivLPU8+Eq4Lrb+tl1LepcOX
OzQeADTSCn5VidOfjDkIst9UXjMlrFfV9/oJEw5Eiqa6lkNPCGDhfA8=
-----END RSA PRIVATE KEY-----

View File

@ -1,24 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEEjCCAvqgAwIBAgIJAM7PFmA6Y+KeMA0GCSqGSIb3DQEBCwUAMIGWMQswCQYD
VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFDASBgNVBAcMC1N0b255IEJyb29r
MRIwEAYDVQQKDAlIYXNoaUNvcnAxDjAMBgNVBAsMBVZhdWx0MRUwEwYDVQQDDAxW
aXNoYWwgTmF5YWsxIzAhBgkqhkiG9w0BCQEWFHZpc2hhbEBoYXNoaWNvcnAuY29t
MB4XDTE1MDgwNzE5MTk1OFoXDTE1MDkwNjE5MTk1OFowgZYxCzAJBgNVBAYTAlVT
MREwDwYDVQQIDAhOZXcgWW9yazEUMBIGA1UEBwwLU3RvbnkgQnJvb2sxEjAQBgNV
BAoMCUhhc2hpQ29ycDEOMAwGA1UECwwFVmF1bHQxFTATBgNVBAMMDFZpc2hhbCBO
YXlhazEjMCEGCSqGSIb3DQEJARYUdmlzaGFsQGhhc2hpY29ycC5jb20wggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcGlPKIrsq5sDJAUB7mtLjnjbcfR0b
dX1sDHUaTdT+2YBq0JvtoLZOmKw1iVwsMBhaLeXwnKP/O/n67sE8zvZPsuU3REw1
NTjPof8IbepkENWNxR68KoSB2Vn5r4KiO3ux+KbkXssrZB62+k9khj0e7qIiwyZP
y5+RQPOL2ESmX5DznX+90vH4mzAEF654PbXFI/qOBZcWvWZJ37i+lHkeyCqcB+sm
5o5+zd1ua8jVlN0eLjyqa7FDvIuXPAFEX+r5DVQgIvS2++YaFRqTFCIxRXdDQXdw
1xDMCuG1w4PGVWf3TtlpHeGSIU07DdrCgXsvIRYfW++aZ2pvXwJYCr8hAgMBAAGj
YTBfMA8GA1UdEQQIMAaHBKwYFugwHQYDVR0OBBYEFPl+AkButpRfbblZE9Jb3xNj
AyhkMB8GA1UdIwQYMBaAFPl+AkButpRfbblZE9Jb3xNjAyhkMAwGA1UdEwQFMAMB
Af8wDQYJKoZIhvcNAQELBQADggEBADdIyyBJ3BVghW1shhxYsqQgg/gj2TagpO1P
ulGNzS0aCfB4tzMD4MGWm7cTlL6QW9W6r9OuWKCd1ADherIX9j0gtVWgIMtWGx+i
NbHrYin1xHr4rkB7/f6veCiJ3CtzBC9P/rEI6keyfOn1BfQBsOxfo3oGe/HDlSzD
lpu0GlQECjTXD7dd4jrD0T/wdRQI0BmxcYjn9cZLgoJHtLHZwaS16TGVmKs4iRAW
V9Aw5hLK4jJ59IID830/ly+Ndfc//QGgdE5PM44OrvVFO3Q8+zs7pwr1ql7uQWew
MSuDfbL7EcEGajD/o085sj2u4xVUfkVBW+3TQvs4/pHYOxlhPjI=
-----END CERTIFICATE-----

View File

@ -1,157 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package audit
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
)
func TestFormatJSON_formatRequest(t *testing.T) {
salter, err := salt.NewSalt(context.Background(), nil, nil)
if err != nil {
t.Fatal(err)
}
saltFunc := func(context.Context) (*salt.Salt, error) {
return salter, nil
}
expectedResultStr := fmt.Sprintf(testFormatJSONReqBasicStrFmt, salter.GetIdentifiedHMAC("foo"))
issueTime, _ := time.Parse(time.RFC3339, "2020-05-28T13:40:18-05:00")
cases := map[string]struct {
Auth *logical.Auth
Req *logical.Request
Err error
Prefix string
ExpectedStr string
}{
"auth, request": {
&logical.Auth{
ClientToken: "foo",
Accessor: "bar",
DisplayName: "testtoken",
EntityID: "foobarentity",
NoDefaultPolicy: true,
Policies: []string{"root"},
TokenType: logical.TokenTypeService,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour * 4,
IssueTime: issueTime,
},
},
&logical.Request{
Operation: logical.UpdateOperation,
Path: "/foo",
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
Headers: map[string][]string{
"foo": {"bar"},
},
},
errors.New("this is an error"),
"",
expectedResultStr,
},
"auth, request with prefix": {
&logical.Auth{
ClientToken: "foo",
Accessor: "bar",
EntityID: "foobarentity",
DisplayName: "testtoken",
NoDefaultPolicy: true,
Policies: []string{"root"},
TokenType: logical.TokenTypeService,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour * 4,
IssueTime: issueTime,
},
},
&logical.Request{
Operation: logical.UpdateOperation,
Path: "/foo",
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
Headers: map[string][]string{
"foo": {"bar"},
},
},
errors.New("this is an error"),
"@cee: ",
expectedResultStr,
},
}
for name, tc := range cases {
var buf bytes.Buffer
formatter := AuditFormatter{
AuditFormatWriter: &JSONFormatWriter{
Prefix: tc.Prefix,
SaltFunc: saltFunc,
},
}
config := FormatterConfig{
HMACAccessor: false,
}
in := &logical.LogInput{
Auth: tc.Auth,
Request: tc.Req,
OuterErr: tc.Err,
}
if err := formatter.FormatRequest(namespace.RootContext(nil), &buf, config, in); err != nil {
t.Fatalf("bad: %s\nerr: %s", name, err)
}
if !strings.HasPrefix(buf.String(), tc.Prefix) {
t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, expectedResultStr, tc.Prefix)
}
expectedjson := new(AuditRequestEntry)
if err := jsonutil.DecodeJSON([]byte(expectedResultStr), &expectedjson); err != nil {
t.Fatalf("bad json: %s", err)
}
expectedjson.Request.Namespace = &AuditNamespace{ID: "root"}
actualjson := new(AuditRequestEntry)
if err := jsonutil.DecodeJSON([]byte(buf.String())[len(tc.Prefix):], &actualjson); err != nil {
t.Fatalf("bad json: %s", err)
}
expectedjson.Time = actualjson.Time
expectedBytes, err := json.Marshal(expectedjson)
if err != nil {
t.Fatalf("unable to marshal json: %s", err)
}
if !strings.HasSuffix(strings.TrimSpace(buf.String()), string(expectedBytes)) {
t.Fatalf(
"bad: %s\nResult:\n\n%q\n\nExpected:\n\n%q",
name, buf.String(), string(expectedBytes))
}
}
}
const testFormatJSONReqBasicStrFmt = `{"time":"2015-08-05T13:45:46Z","type":"request","auth":{"client_token":"%s","accessor":"bar","display_name":"testtoken","policies":["root"],"no_default_policy":true,"metadata":null,"entity_id":"foobarentity","token_type":"service", "token_ttl": 14400, "token_issue_time": "2020-05-28T13:40:18-05:00"},"request":{"operation":"update","path":"/foo","data":null,"wrap_ttl":60,"remote_address":"127.0.0.1","headers":{"foo":["bar"]}},"error":"this is an error"}
`

View File

@ -1,147 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package audit
import (
"bytes"
"context"
"errors"
"fmt"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
)
func TestFormatJSONx_formatRequest(t *testing.T) {
salter, err := salt.NewSalt(context.Background(), nil, nil)
if err != nil {
t.Fatal(err)
}
saltFunc := func(context.Context) (*salt.Salt, error) {
return salter, nil
}
fooSalted := salter.GetIdentifiedHMAC("foo")
issueTime, _ := time.Parse(time.RFC3339, "2020-05-28T13:40:18-05:00")
cases := map[string]struct {
Auth *logical.Auth
Req *logical.Request
Err error
Prefix string
Result string
ExpectedStr string
}{
"auth, request": {
&logical.Auth{
ClientToken: "foo",
Accessor: "bar",
DisplayName: "testtoken",
EntityID: "foobarentity",
NoDefaultPolicy: true,
Policies: []string{"root"},
TokenType: logical.TokenTypeService,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour * 4,
IssueTime: issueTime,
},
},
&logical.Request{
ID: "request",
ClientToken: "foo",
ClientTokenAccessor: "bar",
Operation: logical.UpdateOperation,
Path: "/foo",
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
Headers: map[string][]string{
"foo": {"bar"},
},
PolicyOverride: true,
},
errors.New("this is an error"),
"",
"",
fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id">foobarentity</json:string><json:boolean name="no_default_policy">true</json:boolean><json:array name="policies"><json:string>root</json:string></json:array><json:string name="token_issue_time">2020-05-28T13:40:18-05:00</json:string><json:number name="token_ttl">14400</json:number><json:string name="token_type">service</json:string></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token">%s</json:string><json:string name="client_token_accessor">bar</json:string><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id">request</json:string><json:object name="namespace"><json:string name="id">root</json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">true</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`,
fooSalted, fooSalted),
},
"auth, request with prefix": {
&logical.Auth{
ClientToken: "foo",
Accessor: "bar",
DisplayName: "testtoken",
NoDefaultPolicy: true,
EntityID: "foobarentity",
Policies: []string{"root"},
TokenType: logical.TokenTypeService,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour * 4,
IssueTime: issueTime,
},
},
&logical.Request{
ID: "request",
ClientToken: "foo",
ClientTokenAccessor: "bar",
Operation: logical.UpdateOperation,
Path: "/foo",
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
Headers: map[string][]string{
"foo": {"bar"},
},
PolicyOverride: true,
},
errors.New("this is an error"),
"",
"@cee: ",
fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id">foobarentity</json:string><json:boolean name="no_default_policy">true</json:boolean><json:array name="policies"><json:string>root</json:string></json:array><json:string name="token_issue_time">2020-05-28T13:40:18-05:00</json:string><json:number name="token_ttl">14400</json:number><json:string name="token_type">service</json:string></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token">%s</json:string><json:string name="client_token_accessor">bar</json:string><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id">request</json:string><json:object name="namespace"><json:string name="id">root</json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">true</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`,
fooSalted, fooSalted),
},
}
for name, tc := range cases {
var buf bytes.Buffer
formatter := AuditFormatter{
AuditFormatWriter: &JSONxFormatWriter{
Prefix: tc.Prefix,
SaltFunc: saltFunc,
},
}
config := FormatterConfig{
OmitTime: true,
HMACAccessor: false,
}
in := &logical.LogInput{
Auth: tc.Auth,
Request: tc.Req,
OuterErr: tc.Err,
}
if err := formatter.FormatRequest(namespace.RootContext(nil), &buf, config, in); err != nil {
t.Fatalf("bad: %s\nerr: %s", name, err)
}
if !strings.HasPrefix(buf.String(), tc.Prefix) {
t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, tc.Result, tc.Prefix)
}
if !strings.HasSuffix(strings.TrimSpace(buf.String()), string(tc.ExpectedStr)) {
t.Fatalf(
"bad: %s\nResult:\n\n%q\n\nExpected:\n\n%q",
name, strings.TrimSpace(buf.String()), string(tc.ExpectedStr))
}
}
}

View File

@ -1,224 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package audit
import (
"context"
"io"
"testing"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testingFormatWriter struct {
salt *salt.Salt
lastRequest *AuditRequestEntry
lastResponse *AuditResponseEntry
}
func (fw *testingFormatWriter) WriteRequest(_ io.Writer, entry *AuditRequestEntry) error {
fw.lastRequest = entry
return nil
}
func (fw *testingFormatWriter) WriteResponse(_ io.Writer, entry *AuditResponseEntry) error {
fw.lastResponse = entry
return nil
}
func (fw *testingFormatWriter) Salt(ctx context.Context) (*salt.Salt, error) {
if fw.salt != nil {
return fw.salt, nil
}
var err error
fw.salt, err = salt.NewSalt(ctx, nil, nil)
if err != nil {
return nil, err
}
return fw.salt, nil
}
// hashExpectedValueForComparison replicates enough of the audit HMAC process on a piece of expected data in a test,
// so that we can use assert.Equal to compare the expected and output values.
func (fw *testingFormatWriter) hashExpectedValueForComparison(input map[string]interface{}) map[string]interface{} {
// Copy input before modifying, since we may re-use the same data in another test
copied, err := copystructure.Copy(input)
if err != nil {
panic(err)
}
copiedAsMap := copied.(map[string]interface{})
salter, err := fw.Salt(context.Background())
if err != nil {
panic(err)
}
err = hashMap(salter.GetIdentifiedHMAC, copiedAsMap, nil)
if err != nil {
panic(err)
}
return copiedAsMap
}
func TestFormatRequestErrors(t *testing.T) {
config := FormatterConfig{}
formatter := AuditFormatter{
AuditFormatWriter: &testingFormatWriter{},
}
if err := formatter.FormatRequest(context.Background(), io.Discard, config, &logical.LogInput{}); err == nil {
t.Fatal("expected error due to nil request")
}
in := &logical.LogInput{
Request: &logical.Request{},
}
if err := formatter.FormatRequest(context.Background(), nil, config, in); err == nil {
t.Fatal("expected error due to nil writer")
}
}
func TestFormatResponseErrors(t *testing.T) {
config := FormatterConfig{}
formatter := AuditFormatter{
AuditFormatWriter: &testingFormatWriter{},
}
if err := formatter.FormatResponse(context.Background(), io.Discard, config, &logical.LogInput{}); err == nil {
t.Fatal("expected error due to nil request")
}
in := &logical.LogInput{
Request: &logical.Request{},
}
if err := formatter.FormatResponse(context.Background(), nil, config, in); err == nil {
t.Fatal("expected error due to nil writer")
}
}
func TestElideListResponses(t *testing.T) {
tfw := testingFormatWriter{}
formatter := AuditFormatter{&tfw}
ctx := namespace.RootContext(context.Background())
type test struct {
name string
inputData map[string]interface{}
expectedData map[string]interface{}
}
tests := []test{
{
"nil data",
nil,
nil,
},
{
"Normal list (keys only)",
map[string]interface{}{
"keys": []string{"foo", "bar", "baz"},
},
map[string]interface{}{
"keys": 3,
},
},
{
"Enhanced list (has key_info)",
map[string]interface{}{
"keys": []string{"foo", "bar", "baz", "quux"},
"key_info": map[string]interface{}{
"foo": "alpha",
"bar": "beta",
"baz": "gamma",
"quux": "delta",
},
},
map[string]interface{}{
"keys": 4,
"key_info": 4,
},
},
{
"Unconventional other values in a list response are not touched",
map[string]interface{}{
"keys": []string{"foo", "bar"},
"something_else": "baz",
},
map[string]interface{}{
"keys": 2,
"something_else": "baz",
},
},
{
"Conventional values in a list response are not elided if their data types are unconventional",
map[string]interface{}{
"keys": map[string]interface{}{
"You wouldn't expect keys to be a map": nil,
},
"key_info": []string{
"You wouldn't expect key_info to be a slice",
},
},
map[string]interface{}{
"keys": map[string]interface{}{
"You wouldn't expect keys to be a map": nil,
},
"key_info": []string{
"You wouldn't expect key_info to be a slice",
},
},
},
}
oneInterestingTestCase := tests[2]
formatResponse := func(
t *testing.T,
config FormatterConfig,
operation logical.Operation,
inputData map[string]interface{},
) {
err := formatter.FormatResponse(ctx, io.Discard, config, &logical.LogInput{
Request: &logical.Request{Operation: operation},
Response: &logical.Response{Data: inputData},
})
require.Nil(t, err)
}
t.Run("Default case", func(t *testing.T) {
config := FormatterConfig{ElideListResponses: true}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
formatResponse(t, config, logical.ListOperation, tc.inputData)
assert.Equal(t, tfw.hashExpectedValueForComparison(tc.expectedData), tfw.lastResponse.Response.Data)
})
}
})
t.Run("When Operation is not list, eliding does not happen", func(t *testing.T) {
config := FormatterConfig{ElideListResponses: true}
tc := oneInterestingTestCase
formatResponse(t, config, logical.ReadOperation, tc.inputData)
assert.Equal(t, tfw.hashExpectedValueForComparison(tc.inputData), tfw.lastResponse.Response.Data)
})
t.Run("When ElideListResponses is false, eliding does not happen", func(t *testing.T) {
config := FormatterConfig{ElideListResponses: false}
tc := oneInterestingTestCase
formatResponse(t, config, logical.ListOperation, tc.inputData)
assert.Equal(t, tfw.hashExpectedValueForComparison(tc.inputData), tfw.lastResponse.Response.Data)
})
t.Run("When Raw is true, eliding still happens", func(t *testing.T) {
config := FormatterConfig{ElideListResponses: true, Raw: true}
tc := oneInterestingTestCase
formatResponse(t, config, logical.ListOperation, tc.inputData)
assert.Equal(t, tc.expectedData, tfw.lastResponse.Response.Data)
})
}

View File

@ -1,400 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package audit
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
"github.com/go-test/deep"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/helper/wrapping"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/copystructure"
)
func TestCopy_auth(t *testing.T) {
// Make a non-pointer one so that it can't be modified directly
expected := logical.Auth{
LeaseOptions: logical.LeaseOptions{
TTL: 1 * time.Hour,
},
ClientToken: "foo",
}
auth := expected
// Copy it
dup, err := copystructure.Copy(&auth)
if err != nil {
t.Fatalf("err: %s", err)
}
// Check equality
auth2 := dup.(*logical.Auth)
if !reflect.DeepEqual(*auth2, expected) {
t.Fatalf("bad:\n\n%#v\n\n%#v", *auth2, expected)
}
}
func TestCopy_request(t *testing.T) {
// Make a non-pointer one so that it can't be modified directly
expected := logical.Request{
Data: map[string]interface{}{
"foo": "bar",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
}
arg := expected
// Copy it
dup, err := copystructure.Copy(&arg)
if err != nil {
t.Fatalf("err: %s", err)
}
// Check equality
arg2 := dup.(*logical.Request)
if !reflect.DeepEqual(*arg2, expected) {
t.Fatalf("bad:\n\n%#v\n\n%#v", *arg2, expected)
}
}
func TestCopy_response(t *testing.T) {
// Make a non-pointer one so that it can't be modified directly
expected := logical.Response{
Data: map[string]interface{}{
"foo": "bar",
},
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "foo",
CreationTime: time.Now(),
WrappedAccessor: "abcd1234",
},
}
arg := expected
// Copy it
dup, err := copystructure.Copy(&arg)
if err != nil {
t.Fatalf("err: %s", err)
}
// Check equality
arg2 := dup.(*logical.Response)
if !reflect.DeepEqual(*arg2, expected) {
t.Fatalf("bad:\n\n%#v\n\n%#v", *arg2, expected)
}
}
func TestHashString(t *testing.T) {
inmemStorage := &logical.InmemStorage{}
inmemStorage.Put(context.Background(), &logical.StorageEntry{
Key: "salt",
Value: []byte("foo"),
})
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
HMAC: sha256.New,
HMACType: "hmac-sha256",
})
if err != nil {
t.Fatalf("Error instantiating salt: %s", err)
}
out := HashString(localSalt, "foo")
if out != "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a" {
t.Fatalf("err: HashString output did not match expected")
}
}
func TestHashAuth(t *testing.T) {
cases := []struct {
Input *logical.Auth
Output *logical.Auth
HMACAccessor bool
}{
{
&logical.Auth{ClientToken: "foo"},
&logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"},
false,
},
{
&logical.Auth{
LeaseOptions: logical.LeaseOptions{
TTL: 1 * time.Hour,
},
ClientToken: "foo",
},
&logical.Auth{
LeaseOptions: logical.LeaseOptions{
TTL: 1 * time.Hour,
},
ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a",
},
false,
},
}
inmemStorage := &logical.InmemStorage{}
inmemStorage.Put(context.Background(), &logical.StorageEntry{
Key: "salt",
Value: []byte("foo"),
})
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
HMAC: sha256.New,
HMACType: "hmac-sha256",
})
if err != nil {
t.Fatalf("Error instantiating salt: %s", err)
}
for _, tc := range cases {
input := fmt.Sprintf("%#v", tc.Input)
out, err := HashAuth(localSalt, tc.Input, tc.HMACAccessor)
if err != nil {
t.Fatalf("err: %s\n\n%s", err, input)
}
if !reflect.DeepEqual(out, tc.Output) {
t.Fatalf("bad:\nInput:\n%s\nOutput:\n%#v\nExpected output:\n%#v", input, out, tc.Output)
}
}
}
type testOptMarshaler struct {
S string
I int
}
func (o *testOptMarshaler) MarshalJSONWithOptions(options *logical.MarshalOptions) ([]byte, error) {
return json.Marshal(&testOptMarshaler{S: options.ValueHasher(o.S), I: o.I})
}
var _ logical.OptMarshaler = &testOptMarshaler{}
func TestHashRequest(t *testing.T) {
cases := []struct {
Input *logical.Request
Output *logical.Request
NonHMACDataKeys []string
HMACAccessor bool
}{
{
&logical.Request{
Data: map[string]interface{}{
"foo": "bar",
"baz": "foobar",
"private_key_type": certutil.PrivateKeyType("rsa"),
"om": &testOptMarshaler{S: "bar", I: 1},
},
},
&logical.Request{
Data: map[string]interface{}{
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
"baz": "foobar",
"private_key_type": "hmac-sha256:995230dca56fffd310ff591aa404aab52b2abb41703c787cfa829eceb4595bf1",
"om": json.RawMessage(`{"S":"hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317","I":1}`),
},
},
[]string{"baz"},
false,
},
}
inmemStorage := &logical.InmemStorage{}
inmemStorage.Put(context.Background(), &logical.StorageEntry{
Key: "salt",
Value: []byte("foo"),
})
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
HMAC: sha256.New,
HMACType: "hmac-sha256",
})
if err != nil {
t.Fatalf("Error instantiating salt: %s", err)
}
for _, tc := range cases {
input := fmt.Sprintf("%#v", tc.Input)
out, err := HashRequest(localSalt, tc.Input, tc.HMACAccessor, tc.NonHMACDataKeys)
if err != nil {
t.Fatalf("err: %s\n\n%s", err, input)
}
if diff := deep.Equal(out, tc.Output); len(diff) > 0 {
t.Fatalf("bad:\nInput:\n%s\nDiff:\n%#v", input, diff)
}
}
}
func TestHashResponse(t *testing.T) {
now := time.Now()
cases := []struct {
Input *logical.Response
Output *logical.Response
NonHMACDataKeys []string
HMACAccessor bool
}{
{
&logical.Response{
Data: map[string]interface{}{
"foo": "bar",
"baz": "foobar",
// Responses can contain time values, so test that with
// a known fixed value.
"bar": now,
"om": &testOptMarshaler{S: "bar", I: 1},
},
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "bar",
Accessor: "flimflam",
CreationTime: now,
WrappedAccessor: "bar",
},
},
&logical.Response{
Data: map[string]interface{}{
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
"baz": "foobar",
"bar": now.Format(time.RFC3339Nano),
"om": json.RawMessage(`{"S":"hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317","I":1}`),
},
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390",
CreationTime: now,
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
},
},
[]string{"baz"},
true,
},
}
inmemStorage := &logical.InmemStorage{}
inmemStorage.Put(context.Background(), &logical.StorageEntry{
Key: "salt",
Value: []byte("foo"),
})
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
HMAC: sha256.New,
HMACType: "hmac-sha256",
})
if err != nil {
t.Fatalf("Error instantiating salt: %s", err)
}
for _, tc := range cases {
input := fmt.Sprintf("%#v", tc.Input)
out, err := HashResponse(localSalt, tc.Input, tc.HMACAccessor, tc.NonHMACDataKeys, false)
if err != nil {
t.Fatalf("err: %s\n\n%s", err, input)
}
if diff := deep.Equal(out, tc.Output); len(diff) > 0 {
t.Fatalf("bad:\nInput:\n%s\nDiff:\n%#v", input, diff)
}
}
}
func TestHashWalker(t *testing.T) {
replaceText := "foo"
cases := []struct {
Input map[string]interface{}
Output map[string]interface{}
}{
{
map[string]interface{}{
"hello": "foo",
},
map[string]interface{}{
"hello": replaceText,
},
},
{
map[string]interface{}{
"hello": []interface{}{"world"},
},
map[string]interface{}{
"hello": []interface{}{replaceText},
},
},
}
for _, tc := range cases {
err := HashStructure(tc.Input, func(string) string {
return replaceText
}, nil)
if err != nil {
t.Fatalf("err: %s\n\n%#v", err, tc.Input)
}
if !reflect.DeepEqual(tc.Input, tc.Output) {
t.Fatalf("bad:\n\n%#v\n\n%#v", tc.Input, tc.Output)
}
}
}
func TestHashWalker_TimeStructs(t *testing.T) {
replaceText := "bar"
now := time.Now()
cases := []struct {
Input map[string]interface{}
Output map[string]interface{}
}{
// Should not touch map keys of type time.Time.
{
map[string]interface{}{
"hello": map[time.Time]struct{}{
now: {},
},
},
map[string]interface{}{
"hello": map[time.Time]struct{}{
now: {},
},
},
},
// Should handle map values of type time.Time.
{
map[string]interface{}{
"hello": now,
},
map[string]interface{}{
"hello": now.Format(time.RFC3339Nano),
},
},
// Should handle slice values of type time.Time.
{
map[string]interface{}{
"hello": []interface{}{"foo", now, "foo2"},
},
map[string]interface{}{
"hello": []interface{}{"foobar", now.Format(time.RFC3339Nano), "foo2bar"},
},
},
}
for _, tc := range cases {
err := HashStructure(tc.Input, func(s string) string {
return s + replaceText
}, nil)
if err != nil {
t.Fatalf("err: %v\n\n%#v", err, tc.Input)
}
if !reflect.DeepEqual(tc.Input, tc.Output) {
t.Fatalf("bad:\n\n%#v\n\n%#v", tc.Input, tc.Output)
}
}
}

View File

@ -1,186 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package file
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
)
func TestAuditFile_fileModeNew(t *testing.T) {
modeStr := "0777"
mode, err := strconv.ParseUint(modeStr, 8, 32)
if err != nil {
t.Fatal(err)
}
path, err := ioutil.TempDir("", "vault-test_audit_file-file_mode_new")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(path)
file := filepath.Join(path, "auditTest.txt")
config := map[string]string{
"path": file,
"mode": modeStr,
}
_, err = Factory(context.Background(), &audit.BackendConfig{
SaltConfig: &salt.Config{},
SaltView: &logical.InmemStorage{},
Config: config,
})
if err != nil {
t.Fatal(err)
}
info, err := os.Stat(file)
if err != nil {
t.Fatalf("Cannot retrieve file mode from `Stat`")
}
if info.Mode() != os.FileMode(mode) {
t.Fatalf("File mode does not match.")
}
}
func TestAuditFile_fileModeExisting(t *testing.T) {
f, err := ioutil.TempFile("", "test")
if err != nil {
t.Fatalf("Failure to create test file.")
}
defer os.Remove(f.Name())
err = os.Chmod(f.Name(), 0o777)
if err != nil {
t.Fatalf("Failure to chmod temp file for testing.")
}
err = f.Close()
if err != nil {
t.Fatalf("Failure to close temp file for test.")
}
config := map[string]string{
"path": f.Name(),
}
_, err = Factory(context.Background(), &audit.BackendConfig{
Config: config,
SaltConfig: &salt.Config{},
SaltView: &logical.InmemStorage{},
})
if err != nil {
t.Fatal(err)
}
info, err := os.Stat(f.Name())
if err != nil {
t.Fatalf("cannot retrieve file mode from `Stat`")
}
if info.Mode() != os.FileMode(0o600) {
t.Fatalf("File mode does not match.")
}
}
func TestAuditFile_fileMode0000(t *testing.T) {
f, err := ioutil.TempFile("", "test")
if err != nil {
t.Fatalf("Failure to create test file. The error is %v", err)
}
defer os.Remove(f.Name())
err = os.Chmod(f.Name(), 0o777)
if err != nil {
t.Fatalf("Failure to chmod temp file for testing. The error is %v", err)
}
err = f.Close()
if err != nil {
t.Fatalf("Failure to close temp file for test. The error is %v", err)
}
config := map[string]string{
"path": f.Name(),
"mode": "0000",
}
_, err = Factory(context.Background(), &audit.BackendConfig{
Config: config,
SaltConfig: &salt.Config{},
SaltView: &logical.InmemStorage{},
})
if err != nil {
t.Fatal(err)
}
info, err := os.Stat(f.Name())
if err != nil {
t.Fatalf("cannot retrieve file mode from `Stat`. The error is %v", err)
}
if info.Mode() != os.FileMode(0o777) {
t.Fatalf("File mode does not match.")
}
}
func BenchmarkAuditFile_request(b *testing.B) {
config := map[string]string{
"path": "/dev/null",
}
sink, err := Factory(context.Background(), &audit.BackendConfig{
Config: config,
SaltConfig: &salt.Config{},
SaltView: &logical.InmemStorage{},
})
if err != nil {
b.Fatal(err)
}
in := &logical.LogInput{
Auth: &logical.Auth{
ClientToken: "foo",
Accessor: "bar",
EntityID: "foobarentity",
DisplayName: "testtoken",
NoDefaultPolicy: true,
Policies: []string{"root"},
TokenType: logical.TokenTypeService,
},
Request: &logical.Request{
Operation: logical.UpdateOperation,
Path: "/foo",
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
Headers: map[string][]string{
"foo": {"bar"},
},
},
}
ctx := namespace.RootContext(nil)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if err := sink.LogRequest(ctx, in); err != nil {
panic(err)
}
}
})
}

View File

@ -1,430 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package approle
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/hashicorp/vault/sdk/logical"
)
func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
t.Helper()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b, err := Backend(config)
if err != nil {
t.Fatal(err)
}
if b == nil {
t.Fatalf("failed to create backend")
}
err = b.Backend.Setup(context.Background(), config)
if err != nil {
t.Fatal(err)
}
return b, config.StorageView
}
func TestAppRole_RoleServiceToBatchNumUses(t *testing.T) {
b, s := createBackendWithStorage(t)
requestFunc := func(operation logical.Operation, data map[string]interface{}) {
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Path: "role/testrole",
Operation: operation,
Storage: s,
Data: data,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %#v\nresp: %#v", err, resp)
}
}
data := map[string]interface{}{
"bind_secret_id": true,
"secret_id_num_uses": 0,
"secret_id_ttl": "10m",
"token_policies": "policy",
"token_ttl": "5m",
"token_max_ttl": "10m",
"token_num_uses": 2,
"token_type": "default",
}
requestFunc(logical.CreateOperation, data)
data["token_num_uses"] = 0
data["token_type"] = "batch"
requestFunc(logical.UpdateOperation, data)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Path: "role/testrole/role-id",
Operation: logical.ReadOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
roleID := resp.Data["role_id"]
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/testrole/secret-id",
Operation: logical.UpdateOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
secretID := resp.Data["secret_id"]
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
require.NotNil(t, resp.Auth)
}
func TestAppRole_RoleNameCaseSensitivity(t *testing.T) {
testFunc := func(t *testing.T, roleName string) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Create the role
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName,
Operation: logical.CreateOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%v", resp, err)
}
// Get the role-id
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/role-id",
Operation: logical.ReadOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
roleID := resp.Data["role_id"]
// Create a secret-id
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/secret-id",
Operation: logical.UpdateOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
secretID := resp.Data["secret_id"]
secretIDAccessor := resp.Data["secret_id_accessor"]
// Ensure login works
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp.Auth == nil {
t.Fatalf("failed to perform login")
}
// Destroy secret ID accessor
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/secret-id-accessor/destroy",
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"secret_id_accessor": secretIDAccessor,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Login again using the accessor's corresponding secret ID should fail
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil && err != logical.ErrInvalidCredentials {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected error due to invalid secret ID")
}
// Generate another secret ID
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/secret-id",
Operation: logical.UpdateOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
secretID = resp.Data["secret_id"]
secretIDAccessor = resp.Data["secret_id_accessor"]
// Ensure login works
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp.Auth == nil {
t.Fatalf("failed to perform login")
}
// Destroy the secret ID
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/secret-id/destroy",
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"secret_id": secretID,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Login again using the same secret ID should fail
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil && err != logical.ErrInvalidCredentials {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected error due to invalid secret ID")
}
// Generate another secret ID
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/secret-id",
Operation: logical.UpdateOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
secretID = resp.Data["secret_id"]
secretIDAccessor = resp.Data["secret_id_accessor"]
// Ensure login works
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp.Auth == nil {
t.Fatalf("failed to perform login")
}
// Destroy the secret ID using lower cased role name
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + strings.ToLower(roleName) + "/secret-id/destroy",
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"secret_id": secretID,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Login again using the same secret ID should fail
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil && err != logical.ErrInvalidCredentials {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected error due to invalid secret ID")
}
// Generate another secret ID
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/secret-id",
Operation: logical.UpdateOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
secretID = resp.Data["secret_id"]
secretIDAccessor = resp.Data["secret_id_accessor"]
// Ensure login works
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp.Auth == nil {
t.Fatalf("failed to perform login")
}
// Destroy the secret ID using upper cased role name
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + strings.ToUpper(roleName) + "/secret-id/destroy",
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"secret_id": secretID,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Login again using the same secret ID should fail
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil && err != logical.ErrInvalidCredentials {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected error due to invalid secret ID")
}
// Generate another secret ID
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/" + roleName + "/secret-id",
Operation: logical.UpdateOperation,
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
secretID = resp.Data["secret_id"]
secretIDAccessor = resp.Data["secret_id_accessor"]
// Ensure login works
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
if resp.Auth == nil {
t.Fatalf("failed to perform login")
}
// Destroy the secret ID using mixed case name
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "role/saMpleRolEnaMe/secret-id/destroy",
Operation: logical.UpdateOperation,
Storage: s,
Data: map[string]interface{}{
"secret_id": secretID,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
// Login again using the same secret ID should fail
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
})
if err != nil && err != logical.ErrInvalidCredentials {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected error due to invalid secret ID")
}
}
// Lower case role name
testFunc(t, "samplerolename")
// Upper case role name
testFunc(t, "SAMPLEROLENAME")
// Mixed case role name
testFunc(t, "SampleRoleName")
}

View File

@ -1,360 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package approle
import (
"context"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/sdk/logical"
)
func TestAppRole_BoundCIDRLogin(t *testing.T) {
var resp *logical.Response
var err error
b, s := createBackendWithStorage(t)
// Create a role with secret ID binding disabled and only bound cidr list
// enabled
resp = b.requestNoErr(t, &logical.Request{
Path: "role/testrole",
Operation: logical.CreateOperation,
Data: map[string]interface{}{
"bind_secret_id": false,
"bound_cidr_list": []string{"127.0.0.1/8"},
"token_bound_cidrs": []string{"10.0.0.0/8"},
},
Storage: s,
})
// Read the role ID
resp = b.requestNoErr(t, &logical.Request{
Path: "role/testrole/role-id",
Operation: logical.ReadOperation,
Storage: s,
})
roleID := resp.Data["role_id"]
// Fill in the connection information and login with just the role ID
resp = b.requestNoErr(t, &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
},
Storage: s,
Connection: &logical.Connection{RemoteAddr: "127.0.0.1"},
})
if resp.Auth == nil {
t.Fatal("expected login to succeed")
}
if len(resp.Auth.BoundCIDRs) != 1 {
t.Fatal("bad token bound cidrs")
}
if resp.Auth.BoundCIDRs[0].String() != "10.0.0.0/8" {
t.Fatalf("bad: %s", resp.Auth.BoundCIDRs[0].String())
}
// Override with a secret-id value, verify it doesn't pass
resp = b.requestNoErr(t, &logical.Request{
Path: "role/testrole",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"bind_secret_id": true,
},
Storage: s,
})
roleSecretIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/testrole/secret-id",
Storage: s,
Data: map[string]interface{}{
"token_bound_cidrs": []string{"11.0.0.0/24"},
},
}
resp, err = b.HandleRequest(context.Background(), roleSecretIDReq)
if err == nil {
t.Fatal("expected error due to mismatching subnet relationship")
}
roleSecretIDReq.Data["token_bound_cidrs"] = "10.0.0.0/24"
resp = b.requestNoErr(t, roleSecretIDReq)
secretID := resp.Data["secret_id"]
resp = b.requestNoErr(t, &logical.Request{
Path: "login",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
Storage: s,
Connection: &logical.Connection{RemoteAddr: "127.0.0.1"},
})
if resp.Auth == nil {
t.Fatal("expected login to succeed")
}
if len(resp.Auth.BoundCIDRs) != 1 {
t.Fatal("bad token bound cidrs")
}
if resp.Auth.BoundCIDRs[0].String() != "10.0.0.0/24" {
t.Fatalf("bad: %s", resp.Auth.BoundCIDRs[0].String())
}
}
func TestAppRole_RoleLogin(t *testing.T) {
var resp *logical.Response
var err error
b, storage := createBackendWithStorage(t)
createRole(t, b, storage, "role1", "a,b,c")
roleRoleIDReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "role/role1/role-id",
Storage: storage,
}
resp = b.requestNoErr(t, roleRoleIDReq)
roleID := resp.Data["role_id"]
roleSecretIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/role1/secret-id",
Storage: storage,
}
resp = b.requestNoErr(t, roleSecretIDReq)
secretID := resp.Data["secret_id"]
loginData := map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
}
loginReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "login",
Storage: storage,
Data: loginData,
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
}
loginResp, err := b.HandleRequest(context.Background(), loginReq)
if err != nil || (loginResp != nil && loginResp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, loginResp)
}
if loginResp.Auth == nil {
t.Fatalf("expected a non-nil auth object in the response")
}
if loginResp.Auth.Metadata == nil {
t.Fatalf("expected a non-nil metadata object in the response")
}
if val := loginResp.Auth.Metadata["role_name"]; val != "role1" {
t.Fatalf("expected metadata.role_name to equal 'role1', got: %v", val)
}
if loginResp.Auth.Alias.Metadata == nil {
t.Fatalf("expected a non-nil alias metadata object in the response")
}
if val := loginResp.Auth.Alias.Metadata["role_name"]; val != "role1" {
t.Fatalf("expected metadata.alias.role_name to equal 'role1', got: %v", val)
}
// Test renewal
renewReq := generateRenewRequest(storage, loginResp.Auth)
resp, err = b.HandleRequest(context.Background(), renewReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Auth.TTL != 400*time.Second {
t.Fatalf("expected period value from response to be 400s, got: %s", resp.Auth.TTL)
}
///
// Test renewal with period
///
// Create role
period := 600 * time.Second
roleData := map[string]interface{}{
"policies": "a,b,c",
"period": period.String(),
}
roleReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "role/" + "role-period",
Storage: storage,
Data: roleData,
}
resp = b.requestNoErr(t, roleReq)
roleRoleIDReq = &logical.Request{
Operation: logical.ReadOperation,
Path: "role/role-period/role-id",
Storage: storage,
}
resp = b.requestNoErr(t, roleRoleIDReq)
roleID = resp.Data["role_id"]
roleSecretIDReq = &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/role-period/secret-id",
Storage: storage,
}
resp = b.requestNoErr(t, roleSecretIDReq)
secretID = resp.Data["secret_id"]
loginData["role_id"] = roleID
loginData["secret_id"] = secretID
loginResp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil || (loginResp != nil && loginResp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, loginResp)
}
if loginResp.Auth == nil {
t.Fatalf("expected a non-nil auth object in the response")
}
renewReq = generateRenewRequest(storage, loginResp.Auth)
resp, err = b.HandleRequest(context.Background(), renewReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp.Auth.Period != period {
t.Fatalf("expected period value of %d in the response, got: %s", period, resp.Auth.Period)
}
// Test input validation with secret_id that exceeds max length
loginData["secret_id"] = strings.Repeat("a", maxHmacInputLength+1)
loginReq = &logical.Request{
Operation: logical.UpdateOperation,
Path: "login",
Storage: storage,
Data: loginData,
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
}
loginResp, err = b.HandleRequest(context.Background(), loginReq)
expectedErr := "failed to create HMAC of secret_id"
if loginResp != nil || err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Fatalf("expected login test to fail with error %q, resp: %#v, err: %v", expectedErr, loginResp, err)
}
}
func generateRenewRequest(s logical.Storage, auth *logical.Auth) *logical.Request {
renewReq := &logical.Request{
Operation: logical.RenewOperation,
Storage: s,
Auth: &logical.Auth{},
}
renewReq.Auth.InternalData = auth.InternalData
renewReq.Auth.Metadata = auth.Metadata
renewReq.Auth.LeaseOptions = auth.LeaseOptions
renewReq.Auth.Policies = auth.Policies
renewReq.Auth.Period = auth.Period
return renewReq
}
func TestAppRole_RoleResolve(t *testing.T) {
b, storage := createBackendWithStorage(t)
role := "role1"
createRole(t, b, storage, role, "a,b,c")
roleRoleIDReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "role/role1/role-id",
Storage: storage,
}
resp := b.requestNoErr(t, roleRoleIDReq)
roleID := resp.Data["role_id"]
roleSecretIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/role1/secret-id",
Storage: storage,
}
resp = b.requestNoErr(t, roleSecretIDReq)
secretID := resp.Data["secret_id"]
loginData := map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
}
loginReq := &logical.Request{
Operation: logical.ResolveRoleOperation,
Path: "login",
Storage: storage,
Data: loginData,
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
}
resp = b.requestNoErr(t, loginReq)
if resp.Data["role"] != role {
t.Fatalf("Role was not as expected. Expected %s, received %s", role, resp.Data["role"])
}
}
func TestAppRole_RoleDoesNotExist(t *testing.T) {
var resp *logical.Response
var err error
b, storage := createBackendWithStorage(t)
roleID := "roleDoesNotExist"
loginData := map[string]interface{}{
"role_id": roleID,
"secret_id": "secret",
}
loginReq := &logical.Request{
Operation: logical.ResolveRoleOperation,
Path: "login",
Storage: storage,
Data: loginData,
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
}
resp, err = b.HandleRequest(context.Background(), loginReq)
if resp == nil && !resp.IsError() {
t.Fatalf("Response was not an error: err:%v resp:%#v", err, resp)
}
errString, ok := resp.Data["error"].(string)
if !ok {
t.Fatal("Error not part of response.")
}
if !strings.Contains(errString, "invalid role or secret ID") {
t.Fatalf("Error was not due to invalid role ID. Error: %s", errString)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,216 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package approle
import (
"context"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
"github.com/hashicorp/vault/sdk/logical"
)
func TestAppRole_TidyDanglingAccessors_Normal(t *testing.T) {
b, storage := createBackendWithStorage(t)
// Create a role
createRole(t, b, storage, "role1", "a,b,c")
// Create a secret-id
roleSecretIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/role1/secret-id",
Storage: storage,
}
_ = b.requestNoErr(t, roleSecretIDReq)
accessorHashes, err := storage.List(context.Background(), "accessor/")
if err != nil {
t.Fatal(err)
}
if len(accessorHashes) != 1 {
t.Fatalf("bad: len(accessorHashes); expect 1, got %d", len(accessorHashes))
}
entry1, err := logical.StorageEntryJSON(
"accessor/invalid1",
&secretIDAccessorStorageEntry{
SecretIDHMAC: "samplesecretidhmac",
},
)
if err != nil {
t.Fatal(err)
}
if err := storage.Put(context.Background(), entry1); err != nil {
t.Fatal(err)
}
entry2, err := logical.StorageEntryJSON(
"accessor/invalid2",
&secretIDAccessorStorageEntry{
SecretIDHMAC: "samplesecretidhmac2",
},
)
if err != nil {
t.Fatal(err)
}
if err := storage.Put(context.Background(), entry2); err != nil {
t.Fatal(err)
}
accessorHashes, err = storage.List(context.Background(), "accessor/")
if err != nil {
t.Fatal(err)
}
if len(accessorHashes) != 3 {
t.Fatalf("bad: len(accessorHashes); expect 3, got %d", len(accessorHashes))
}
secret, err := b.tidySecretID(context.Background(), &logical.Request{
Storage: storage,
})
if err != nil {
t.Fatal(err)
}
schema.ValidateResponse(
t,
schema.GetResponseSchema(t, pathTidySecretID(b), logical.UpdateOperation),
secret,
true,
)
// It runs async so we give it a bit of time to run
time.Sleep(10 * time.Second)
accessorHashes, err = storage.List(context.Background(), "accessor/")
if err != nil {
t.Fatal(err)
}
if len(accessorHashes) != 1 {
t.Fatalf("bad: len(accessorHashes); expect 1, got %d", len(accessorHashes))
}
}
func TestAppRole_TidyDanglingAccessors_RaceTest(t *testing.T) {
b, storage := createBackendWithStorage(t)
// Create a role
createRole(t, b, storage, "role1", "a,b,c")
// Create an initial entry
roleSecretIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/role1/secret-id",
Storage: storage,
}
_ = b.requestNoErr(t, roleSecretIDReq)
count := 1
wg := &sync.WaitGroup{}
start := time.Now()
for time.Now().Sub(start) < 10*time.Second {
if time.Now().Sub(start) > 100*time.Millisecond && atomic.LoadUint32(b.tidySecretIDCASGuard) == 0 {
secret, err := b.tidySecretID(context.Background(), &logical.Request{
Storage: storage,
})
if err != nil {
t.Fatal(err)
}
schema.ValidateResponse(
t,
schema.GetResponseSchema(t, pathTidySecretID(b), logical.UpdateOperation),
secret,
true,
)
}
wg.Add(1)
go func() {
defer wg.Done()
roleSecretIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/role1/secret-id",
Storage: storage,
}
_ = b.requestNoErr(t, roleSecretIDReq)
}()
entry, err := logical.StorageEntryJSON(
fmt.Sprintf("accessor/invalid%d", count),
&secretIDAccessorStorageEntry{
SecretIDHMAC: "samplesecretidhmac",
},
)
if err != nil {
t.Fatal(err)
}
if err := storage.Put(context.Background(), entry); err != nil {
t.Fatal(err)
}
count++
time.Sleep(100 * time.Microsecond)
}
logger := b.Logger().Named(t.Name())
logger.Info("wrote entries", "count", count)
wg.Wait()
// Let tidy finish
for atomic.LoadUint32(b.tidySecretIDCASGuard) != 0 {
time.Sleep(100 * time.Millisecond)
}
logger.Info("running tidy again")
// Run tidy again
secret, err := b.tidySecretID(context.Background(), &logical.Request{
Storage: storage,
})
if err != nil || len(secret.Warnings) > 0 {
t.Fatal(err, secret.Warnings)
}
schema.ValidateResponse(
t,
schema.GetResponseSchema(t, pathTidySecretID(b), logical.UpdateOperation),
secret,
true,
)
// Wait for tidy to start
for atomic.LoadUint32(b.tidySecretIDCASGuard) == 0 {
time.Sleep(100 * time.Millisecond)
}
// Let tidy finish
for atomic.LoadUint32(b.tidySecretIDCASGuard) != 0 {
time.Sleep(100 * time.Millisecond)
}
accessorHashes, err := storage.List(context.Background(), "accessor/")
if err != nil {
t.Fatal(err)
}
if len(accessorHashes) != count {
t.Fatalf("bad: len(accessorHashes); expect %d, got %d", count, len(accessorHashes))
}
roleHMACs, err := storage.List(context.Background(), secretIDPrefix)
if err != nil {
t.Fatal(err)
}
secretIDs, err := storage.List(context.Background(), fmt.Sprintf("%s%s", secretIDPrefix, roleHMACs[0]))
if err != nil {
t.Fatal(err)
}
if len(secretIDs) != count {
t.Fatalf("bad: len(secretIDs); expect %d, got %d", count, len(secretIDs))
}
}

View File

@ -1,60 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package approle
import (
"context"
"testing"
"github.com/hashicorp/vault/sdk/logical"
)
func TestAppRole_SecretIDNumUsesUpgrade(t *testing.T) {
var resp *logical.Response
var err error
b, storage := createBackendWithStorage(t)
roleData := map[string]interface{}{
"secret_id_num_uses": 10,
}
roleReq := &logical.Request{
Operation: logical.CreateOperation,
Path: "role/role1",
Storage: storage,
Data: roleData,
}
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
secretIDReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "role/role1/secret-id",
Storage: storage,
}
resp, err = b.HandleRequest(context.Background(), secretIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
secretIDReq.Operation = logical.UpdateOperation
secretIDReq.Path = "role/role1/secret-id/lookup"
secretIDReq.Data = map[string]interface{}{
"secret_id": resp.Data["secret_id"].(string),
}
resp, err = b.HandleRequest(context.Background(), secretIDReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Check if the response contains the value set for secret_id_num_uses
if resp.Data["secret_id_num_uses"] != 10 {
t.Fatal("invalid secret_id_num_uses")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,207 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cert
import (
"context"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"net/url"
"sync"
"testing"
"time"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
func TestCRLFetch(t *testing.T) {
storage := &logical.InmemStorage{}
lb, err := Factory(context.Background(), &logical.BackendConfig{
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: 300 * time.Second,
MaxLeaseTTLVal: 1800 * time.Second,
},
StorageView: storage,
})
require.NoError(t, err)
b := lb.(*backend)
closeChan := make(chan bool)
go func() {
t := time.NewTicker(50 * time.Millisecond)
for {
select {
case <-t.C:
b.PeriodicFunc(context.Background(), &logical.Request{Storage: storage})
case <-closeChan:
break
}
}
}()
defer close(closeChan)
if err != nil {
t.Fatalf("error: %s", err)
}
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
require.NoError(t, err)
caPEM, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
require.NoError(t, err)
caKeyPEM, err := ioutil.ReadFile("test-fixtures/keys/key.pem")
require.NoError(t, err)
certPEM, err := ioutil.ReadFile("test-fixtures/keys/cert.pem")
caBundle, err := certutil.ParsePEMBundle(string(caPEM))
require.NoError(t, err)
bundle, err := certutil.ParsePEMBundle(string(certPEM) + "\n" + string(caKeyPEM))
require.NoError(t, err)
// Entry with one cert first
revocationListTemplate := &x509.RevocationList{
RevokedCertificates: []pkix.RevokedCertificate{
{
SerialNumber: big.NewInt(1),
RevocationTime: time.Now(),
},
},
Number: big.NewInt(1),
ThisUpdate: time.Now(),
NextUpdate: time.Now().Add(50 * time.Millisecond),
SignatureAlgorithm: x509.SHA1WithRSA,
}
var crlBytesLock sync.Mutex
crlBytes, err := x509.CreateRevocationList(rand.Reader, revocationListTemplate, caBundle.Certificate, bundle.PrivateKey)
require.NoError(t, err)
var serverURL *url.URL
crlServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Host == serverURL.Host {
crlBytesLock.Lock()
w.Write(crlBytes)
crlBytesLock.Unlock()
} else {
w.WriteHeader(http.StatusNotFound)
}
}))
serverURL, _ = url.Parse(crlServer.URL)
req := &logical.Request{
Connection: &logical.Connection{
ConnState: &connState,
},
Storage: storage,
Auth: &logical.Auth{},
}
fd := &framework.FieldData{
Raw: map[string]interface{}{
"name": "test",
"certificate": string(caPEM),
"policies": "foo,bar",
},
Schema: pathCerts(b).Fields,
}
resp, err := b.pathCertWrite(context.Background(), req, fd)
if err != nil {
t.Fatal(err)
}
empty_login_fd := &framework.FieldData{
Raw: map[string]interface{}{},
Schema: pathLogin(b).Fields,
}
resp, err = b.pathLogin(context.Background(), req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
// Set a bad CRL
fd = &framework.FieldData{
Raw: map[string]interface{}{
"name": "testcrl",
"url": "http://wrongserver.com",
},
Schema: pathCRLs(b).Fields,
}
resp, err = b.pathCRLWrite(context.Background(), req, fd)
if err == nil {
t.Fatal(err)
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
// Set good CRL
fd = &framework.FieldData{
Raw: map[string]interface{}{
"name": "testcrl",
"url": crlServer.URL,
},
Schema: pathCRLs(b).Fields,
}
resp, err = b.pathCRLWrite(context.Background(), req, fd)
if err != nil {
t.Fatal(err)
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
b.crlUpdateMutex.Lock()
if len(b.crls["testcrl"].Serials) != 1 {
t.Fatalf("wrong number of certs in CRL got %d, expected 1", len(b.crls["testcrl"].Serials))
}
b.crlUpdateMutex.Unlock()
// Add a cert to the CRL, then wait to see if it gets automatically picked up
revocationListTemplate.RevokedCertificates = []pkix.RevokedCertificate{
{
SerialNumber: big.NewInt(1),
RevocationTime: revocationListTemplate.RevokedCertificates[0].RevocationTime,
},
{
SerialNumber: big.NewInt(2),
RevocationTime: time.Now(),
},
}
revocationListTemplate.ThisUpdate = time.Now()
revocationListTemplate.NextUpdate = time.Now().Add(1 * time.Minute)
revocationListTemplate.Number = big.NewInt(2)
crlBytesLock.Lock()
crlBytes, err = x509.CreateRevocationList(rand.Reader, revocationListTemplate, caBundle.Certificate, bundle.PrivateKey)
crlBytesLock.Unlock()
require.NoError(t, err)
// Give ourselves a little extra room on slower CI systems to ensure we
// can fetch the new CRL.
corehelpers.RetryUntil(t, 2*time.Second, func() error {
b.crlUpdateMutex.Lock()
defer b.crlUpdateMutex.Unlock()
serialCount := len(b.crls["testcrl"].Serials)
if serialCount != 2 {
return fmt.Errorf("CRL refresh did not occur serial count %d", serialCount)
}
return nil
})
}

View File

@ -1,379 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cert
import (
"context"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"io/ioutil"
"math/big"
mathrand "math/rand"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/sdk/helper/certutil"
"golang.org/x/crypto/ocsp"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
"github.com/hashicorp/vault/sdk/logical"
)
var ocspPort int
var source InMemorySource
type testLogger struct{}
func (t *testLogger) Log(args ...any) {
fmt.Printf("%v", args)
}
func TestMain(m *testing.M) {
source = make(InMemorySource)
listener, err := net.Listen("tcp", ":0")
if err != nil {
return
}
ocspPort = listener.Addr().(*net.TCPAddr).Port
srv := &http.Server{
Addr: "localhost:0",
Handler: NewResponder(&testLogger{}, source, nil),
}
go func() {
srv.Serve(listener)
}()
defer srv.Shutdown(context.Background())
m.Run()
}
func TestCert_RoleResolve(t *testing.T) {
certTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "example.com",
},
DNSNames: []string{"example.com"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
}
tempDir, connState, err := generateTestCertAndConnState(t, certTemplate)
if tempDir != "" {
defer os.RemoveAll(tempDir)
}
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_cert.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{dns: "example.com"}, false),
testAccStepLoginWithName(t, connState, "web"),
testAccStepResolveRoleWithName(t, connState, "web"),
// Test with caching disabled
testAccStepSetRoleCacheSize(t, -1),
testAccStepLoginWithName(t, connState, "web"),
testAccStepResolveRoleWithName(t, connState, "web"),
},
})
}
func testAccStepResolveRoleWithName(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ResolveRoleOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
Check: func(resp *logical.Response) error {
if resp.Data["role"] != certName {
t.Fatalf("Role was not as expected. Expected %s, received %s", certName, resp.Data["role"])
}
return nil
},
Data: map[string]interface{}{
"name": certName,
},
}
}
func TestCert_RoleResolveWithoutProvidingCertName(t *testing.T) {
certTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "example.com",
},
DNSNames: []string{"example.com"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
}
tempDir, connState, err := generateTestCertAndConnState(t, certTemplate)
if tempDir != "" {
defer os.RemoveAll(tempDir)
}
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_cert.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{dns: "example.com"}, false),
testAccStepLoginWithName(t, connState, "web"),
testAccStepResolveRoleWithEmptyDataMap(t, connState, "web"),
testAccStepSetRoleCacheSize(t, -1),
testAccStepLoginWithName(t, connState, "web"),
testAccStepResolveRoleWithEmptyDataMap(t, connState, "web"),
},
})
}
func testAccStepSetRoleCacheSize(t *testing.T, size int) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "config",
Data: map[string]interface{}{
"role_cache_size": size,
},
}
}
func testAccStepResolveRoleWithEmptyDataMap(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ResolveRoleOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
Check: func(resp *logical.Response) error {
if resp.Data["role"] != certName {
t.Fatalf("Role was not as expected. Expected %s, received %s", certName, resp.Data["role"])
}
return nil
},
Data: map[string]interface{}{},
}
}
func testAccStepResolveRoleExpectRoleResolutionToFail(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ResolveRoleOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
ErrorOk: true,
Check: func(resp *logical.Response) error {
if resp == nil && !resp.IsError() {
t.Fatalf("Response was not an error: resp:%#v", resp)
}
errString, ok := resp.Data["error"].(string)
if !ok {
t.Fatal("Error not part of response.")
}
if !strings.Contains(errString, "invalid certificate") {
t.Fatalf("Error was not due to invalid role name. Error: %s", errString)
}
return nil
},
Data: map[string]interface{}{
"name": certName,
},
}
}
func testAccStepResolveRoleOCSPFail(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ResolveRoleOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
ErrorOk: true,
Check: func(resp *logical.Response) error {
if resp == nil || !resp.IsError() {
t.Fatalf("Response was not an error: resp:%#v", resp)
}
errString, ok := resp.Data["error"].(string)
if !ok {
t.Fatal("Error not part of response.")
}
if !strings.Contains(errString, "no chain matching") {
t.Fatalf("Error was not due to OCSP failure. Error: %s", errString)
}
return nil
},
Data: map[string]interface{}{
"name": certName,
},
}
}
func TestCert_RoleResolve_RoleDoesNotExist(t *testing.T) {
certTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "example.com",
},
DNSNames: []string{"example.com"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
}
tempDir, connState, err := generateTestCertAndConnState(t, certTemplate)
if tempDir != "" {
defer os.RemoveAll(tempDir)
}
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_cert.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{dns: "example.com"}, false),
testAccStepLoginWithName(t, connState, "web"),
testAccStepResolveRoleExpectRoleResolutionToFail(t, connState, "notweb"),
},
})
}
func TestCert_RoleResolveOCSP(t *testing.T) {
cases := []struct {
name string
failOpen bool
certStatus int
errExpected bool
}{
{"failFalseGoodCert", false, ocsp.Good, false},
{"failFalseRevokedCert", false, ocsp.Revoked, true},
{"failFalseUnknownCert", false, ocsp.Unknown, true},
{"failTrueGoodCert", true, ocsp.Good, false},
{"failTrueRevokedCert", true, ocsp.Revoked, true},
{"failTrueUnknownCert", true, ocsp.Unknown, false},
}
certTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "example.com",
},
DNSNames: []string{"example.com"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
OCSPServer: []string{fmt.Sprintf("http://localhost:%d", ocspPort)},
}
tempDir, connState, err := generateTestCertAndConnState(t, certTemplate)
if tempDir != "" {
defer os.RemoveAll(tempDir)
}
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_cert.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
issuer := parsePEM(ca)
pkf, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_key.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
pk, err := certutil.ParsePEMBundle(string(pkf))
if err != nil {
t.Fatalf("err: %v", err)
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
resp, err := ocsp.CreateResponse(issuer[0], issuer[0], ocsp.Response{
Status: c.certStatus,
SerialNumber: certTemplate.SerialNumber,
ProducedAt: time.Now(),
ThisUpdate: time.Now(),
NextUpdate: time.Now().Add(time.Hour),
}, pk.PrivateKey)
if err != nil {
t.Fatal(err)
}
source[certTemplate.SerialNumber.String()] = resp
b := testFactory(t)
b.(*backend).ocspClient.ClearCache()
var resolveStep logicaltest.TestStep
var loginStep logicaltest.TestStep
if c.errExpected {
loginStep = testAccStepLoginWithNameInvalid(t, connState, "web")
resolveStep = testAccStepResolveRoleOCSPFail(t, connState, "web")
} else {
loginStep = testAccStepLoginWithName(t, connState, "web")
resolveStep = testAccStepResolveRoleWithName(t, connState, "web")
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testAccStepCertWithExtraParams(t, "web", ca, "foo", allowed{dns: "example.com"}, false,
map[string]interface{}{"ocsp_enabled": true, "ocsp_fail_open": c.failOpen}),
testAccStepReadCertPolicy(t, "web", false, map[string]interface{}{"ocsp_enabled": true, "ocsp_fail_open": c.failOpen}),
loginStep,
resolveStep,
},
})
})
}
}
func serialFromBigInt(serial *big.Int) string {
return strings.TrimSpace(certutil.GetHexFormatted(serial.Bytes(), ":"))
}

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDPjCCAiagAwIBAgIUXiEDuecwua9+j1XHLnconxQ/JBcwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYwMzU4WhgPMjA2
NjA0MjAxNjA0MjhaMBYxFDASBgNVBAMTC215dmF1bHQuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwWPjnTqnkc6acah+wWLmdTK0oCrf2687XVhx
VP3IN897TYzkaBQ2Dn1UM2VEL71sE3OZSVm0UWs5n7UqRuDp6mvkvrT2q5zgh/bV
zg9ZL1AI5H7dY2Rsor95I849ymFpXZooMgNtIQLxIeleBwzTnVSkFl8RqKM7NkjZ
wvBafQEjSsYk9050Bu0GMLgFJYRo1LozJLbwIs5ykG5F5PWTMfRvLCgLBzixPb75
unIJ29nL0yB7zzUdkM8CG1EX8NkjGLEnpRnPa7+RMf8bd10v84cr0JFCUQmoabks
sqVyA825/1we2r5Y8blyXZVIr2lcPyGocLDxz1qT1MqxrNQIywIDAQABo4GBMH8w
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBTo2I+W
3Wb2MBe3OWuj5qCbafavMB8GA1UdIwQYMBaAFBTo2I+W3Wb2MBe3OWuj5qCbafav
MBwGA1UdEQQVMBOCC215dmF1bHQuY29thwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB
AQAyjJzDMzf28yMgiu//2R6LD3+zuLHlfX8+p5JB7WDBT7CgSm89gzMRtD2DvqZQ
6iLbZv/x7Td8bdLsOKf3LDCkZyOygJ0Sr9+6YZdc9heWO8tsO/SbcLhj9/vK8YyV
5fJo+vECW8I5zQLeTKfPqJtTU0zFspv0WYCB96Hsbhd1hTfHmVgjBoxi0YuduAa8
3EHuYPfTYkO3M4QJCoQ+3S6LXSTDqppd1KGAy7QhRU6shd29EpSVxhgqZ+CIOpZu
3RgPOgPqfqcOD/v/SRPqhRf+P5O5Dc/N4ZXTZtfJbaY0qE+smpeQUskVQ2TrSqha
UYpNk7+toZW3Gioo0lBD3gH2
-----END CERTIFICATE-----

View File

@ -1,12 +0,0 @@
-----BEGIN X509 CRL-----
MIIBrjCBlzANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDEwtteXZhdWx0LmNvbRcN
MTYwNTAyMTYxNDMzWhcNMTYwNTA1MTYxNDMzWjArMCkCFCXxxcbS0ATpI2PYrx8d
ACLEQ3B9FxExNjA1MDIxMjE0MzMtMDQwMKAjMCEwHwYDVR0jBBgwFoAUwsRNYCw4
U2won66rMKEJm8inFfgwDQYJKoZIhvcNAQELBQADggEBAD/VvoRK4eaEDzG7Z95b
fHL5ubJGkyvkp8ruNu+rfQp8NLgFVvY6a93Hz7WLOhACkKIWJ63+/4vCfDi5uU0B
HW2FICHdlSQ+6DdGJ6MrgujALlyT+69iF+fPiJ/M1j/N7Am8XPYYcfNdSK6CHtfg
gHNB7E+ubBA7lIw7ucIkoiJjXrSWSXTs9/GzLUImiXJAKQ+JzPYryIsGKXKAwgHh
HB56BnJ2vOs7+6UxQ6fjKTMxYdNgoZ34MhkkxNNhylrEndO6XUvUvC1f/1p1wlzy
xTq2MrMfJHJyu08rkrD+kwMPH2uoVwKyDhXdRBP0QrvQwOsvNEhW8LTKwLWkK17b
fEI=
-----END X509 CRL-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAwWPjnTqnkc6acah+wWLmdTK0oCrf2687XVhxVP3IN897TYzk
aBQ2Dn1UM2VEL71sE3OZSVm0UWs5n7UqRuDp6mvkvrT2q5zgh/bVzg9ZL1AI5H7d
Y2Rsor95I849ymFpXZooMgNtIQLxIeleBwzTnVSkFl8RqKM7NkjZwvBafQEjSsYk
9050Bu0GMLgFJYRo1LozJLbwIs5ykG5F5PWTMfRvLCgLBzixPb75unIJ29nL0yB7
zzUdkM8CG1EX8NkjGLEnpRnPa7+RMf8bd10v84cr0JFCUQmoabkssqVyA825/1we
2r5Y8blyXZVIr2lcPyGocLDxz1qT1MqxrNQIywIDAQABAoIBAD1pBd9ov8t6Surq
sY2hZUM0Hc16r+ln5LcInbx6djjaxvHiWql+OYgyXimP764lPYuTuspjFPKB1SOU
+N7XDxCkwFeayXXHdDlYtZ4gm5Z9mMVOT+j++8xWdxZaqJ56fmX9zOPM2LuR3paB
L52Xgh9EwHJmMApYAzaCvbu8bU+iHeNTW80xabxQrp9VCu/A1BXUX06jK4T+wmjZ
kDA82uQp3dCOF1tv/10HgwqkJj6/1jjM0XUzUZR6iV85S6jrA7wD7gDDeqNO8YHN
08YMRgTKk4pbA7AqoC5xbL3gbSjsjyw48KRq0FkdkjsgV0PJZRMUU9fv9puDa23K
WRPa8LECgYEAyeth5bVH8FXnVXIAAFU6W0WdgCK3VakhjItLw0eoxshuTwbVq64w
CNOB8y1pfP83WiJjX3qRG43NDW07X69J57YKtCCb6KICVUPmecgYZPkmegD1HBQZ
5+Aak+5pIUQuycQ0t65yHGu4Jsju05gEFgdzydFjNANgiPxRzZxzAkkCgYEA9S+y
ZR063oCQDg/GhMLCx19nCJyU44Figh1YCD6kTrsSTECuRpQ5B1F9a+LeZT2wnYxv
+qMvvV+lfVY73f5WZ567u2jSDIsCH34p4g7sE25lKwo+Lhik6EtOehJFs2ZUemaT
Ym7EjqWlC1whrG7P4MnTGzPOVNAGAxsGPtT58nMCgYAs/R8A2VU//UPfy9ioOlUY
RPiEtjd3BIoPEHI+/lZihAHf5bvx1oupS8bmcbXRPeQNVyAhA+QU6ZFIbpAOD7Y9
xFe6LpHOUVqHuOs/MxAMX17tTA1QxkHHYi1JzJLr8I8kMW01h86w+mc7bQWZa4Nt
jReFXfvmeOInY2CumS8e0QKBgC23ow/vj1aFqla04lNG7YK3a0LTz39MVM3mItAG
viRgBV1qghRu9uNCcpx3RPijtBbsZMTbQL+S4gyo06jlD79qfZ7IQMJN+SteHvkj
xykoYHzSAB4gQj9+KzffyFdXMVFRZxHnjYb7o/amSzEXyHMlrtNXqZVu5HAXzeZR
V/m5AoGAAStS43Q7qSJSMfMBITKMdKlqCObnifD77WeR2WHGrpkq26300ggsDpMS
UTmnAAo77lSMmDsdoNn2XZmdeTu1CPoQnoZSE5CqPd5GeHA/hhegVCdeYxSXZJoH
Lhiac+AhCEog/MS1GmVsjynD7eDGVFcsJ6SWuam7doKfrpPqPnE=
-----END RSA PRIVATE KEY-----

View File

@ -1,67 +0,0 @@
vault mount pki
vault mount-tune -max-lease-ttl=438000h pki
vault write pki/root/generate/exported common_name=myvault.com ttl=438000h ip_sans=127.0.0.1
vi cacert.pem
vi cakey.pem
vaultcert.hcl
backend "inmem" {
}
disable_mlock = true
default_lease_ttl = "700h"
max_lease_ttl = "768h"
listener "tcp" {
address = "127.0.0.1:8200"
tls_cert_file = "./cacert.pem"
tls_key_file = "./cakey.pem"
}
========================================
vault mount pki
vault mount-tune -max-lease-ttl=438000h pki
vault write pki/root/generate/exported common_name=myvault.com ttl=438000h max_ttl=438000h ip_sans=127.0.0.1
vi testcacert1.pem
vi testcakey1.pem
vi testcaserial1
vault write pki/config/urls issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
vault write pki/roles/myvault-dot-com allowed_domains=myvault.com allow_subdomains=true ttl=437999h max_ttl=438000h allow_ip_sans=true
vault write pki/issue/myvault-dot-com common_name=cert.myvault.com format=pem ip_sans=127.0.0.1
vi testissuedserial1
vault write pki/issue/myvault-dot-com common_name=cert.myvault.com format=pem ip_sans=127.0.0.1
vi testissuedcert2.pem
vi testissuedkey2.pem
vi testissuedserial2
vault write pki/issue/myvault-dot-com common_name=cert.myvault.com format=pem ip_sans=127.0.0.1
vi testissuedserial3
vault write pki/issue/myvault-dot-com common_name=cert.myvault.com format=pem ip_sans=127.0.0.1
vi testissuedcert4.pem
vi testissuedkey4.pem
vi testissuedserial4
vault write pki/issue/myvault-dot-com common_name=cert.myvault.com format=pem ip_sans=127.0.0.1
vi testissuedserial5
vault write pki/revoke serial_number=$(cat testissuedserial2)
vault write pki/revoke serial_number=$(cat testissuedserial4)
curl -XGET "http://127.0.0.1:8200/v1/pki/crl/pem" -H "x-vault-token:123" > issuedcertcrl
openssl crl -in issuedcertcrl -noout -text
========================================
export VAULT_ADDR='http://127.0.0.1:8200'
vault mount pki
vault mount-tune -max-lease-ttl=438000h pki
vault write pki/root/generate/exported common_name=myvault.com ttl=438000h ip_sans=127.0.0.1
vi testcacert2.pem
vi testcakey2.pem
vi testcaserial2
vi testcacert2leaseid
vault write pki/config/urls issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
vault revoke $(cat testcacert2leaseid)
curl -XGET "http://127.0.0.1:8200/v1/pki/crl/pem" -H "x-vault-token:123" > cacert2crl
openssl crl -in cacert2crl -noout -text

View File

@ -1,12 +0,0 @@
-----BEGIN X509 CRL-----
MIIB2TCBwjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDEwtteXZhdWx0LmNvbRcN
MTYwNTAyMTYxMTA4WhcNMTYwNTA1MTYxMTA4WjBWMCkCFAS6oenLRllQ1MRYcSV+
5ukv2563FxExNjA1MDIxMjExMDgtMDQwMDApAhQaQdPJfbIwE3q4nyYp60lVnZaE
5hcRMTYwNTAyMTIxMTA1LTA0MDCgIzAhMB8GA1UdIwQYMBaAFOuKvPiUG06iHkRX
AOeMiUdBfHFyMA0GCSqGSIb3DQEBCwUAA4IBAQBD2jkeOAmkDdYkAXbmjLGdHaQI
WMS/M+wtFnHVIDVQEmUmj/KPsrkshTZv2UgCHIxBha6y+kXUMQFMg6FwriDTB170
WyJVDVhGg2WjiQjnzrzEI+iOmcpx60sPPXE63J/Zxo4QS5M62RTXRq3909HQTFI5
f3xf0pog8mOrv5uQxO1SACP6YFtdDE2dGOVwoIPuNMTY5vijnj8I9dAw8VrbdoBX
m/Ky56kT+BpmVWHKwQd1nEcP/RHSKbZwwJzJG0BoGM8cvzjITtBmpEF+OZcea81x
p9XJkpfFeiVIgzxks3zTeuQjLF8u+MDcdGt0ztHEbkswjxuk1cCovZe2GFr4
-----END X509 CRL-----

View File

@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIUf+jhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw
MTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS
TRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGn
SgMld6ZWRhNheZhA6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmi
YYMiIWplidMmMO5NTRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5
donyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVG
B+5+AAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABo4H1
MIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm++e
HpyM3p708bgZJuRYEdX1o+UwHwYDVR0jBBgwFoAUncSzT/6HMexyuiU9/7EgHu+o
k5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x
OjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A
AAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br
aS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy+SgMIrwfs
X1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4
aYqNKFWrRaBRAaaYZ/O1ApRTOrXqRx9Eqr0H1BXLsoAq+mWassL8sf6siae+CpwA
KqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU+idkuqfV2h1BQKgSEhFDABjFdTCN
QDAHsEHsi2M4/jRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj
xqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc/J9DIQM+Xmk=
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxSTRAVnygAftetT8pu
HflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGnSgMld6ZWRhNheZhA
6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmiYYMiIWplidMmMO5N
TRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5donyqtnaHuIJGuUd
y54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVGB+5+AAGF5iuHC3N2
DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABAoIBAHR7fFV0eAGaopsX
9OD0TUGlsephBXb43g0GYHfJ/1Ew18w9oaxszJEqkl+PB4W3xZ3yG3e8ZomxDOhF
RreF2WgG5xOfhDogMwu6NodbArfgnAvoC6JnW3qha8HMP4F500RFVyCRcd6A3Frd
rFtaZn/UyCsBAN8/zkwPeYHayo7xX6d9kzgRl9HluEX5PXI5+3uiBDUiM085gkLI
5Cmadh9fMdjfhDXI4x2JYmILpp/9Nlc/krB15s5n1MPNtn3yL0TI0tWp0WlwDCV7
oUm1SfIM0F1fXGFyFDcqwoIr6JCQgXk6XtTg31YhH1xgUIclUVdtHqmAwAbLdIhQ
GAiHn2kCgYEAwD4pZ8HfpiOG/EHNoWsMATc/5yC7O8F9WbvcHZQIymLY4v/7HKZb
VyOR6UQ5/O2cztSGIuKSF6+OK1C34lOyCuTSOTFrjlgEYtLIXjdGLfFdtOO8GRQR
akVXdwuzNAjTBaH5eXbG+NKcjmCvZL48dQVlfDTVulzFGbcsVTHIMQUCgYEA7IQI
FVsKnY3KqpyGqXq92LMcsT3XgW6X1BIIV+YhJ5AFUFkFrjrbXs94/8XyLfi0xBQy
efK+8g5sMs7koF8LyZEcAXWZJQduaKB71hoLlRaU4VQkL/dl2B6VFmAII/CsRCYh
r9RmDN2PF/mp98Ih9dpC1VqcCDRGoTYsd7jLalMCgYAMgH5k1wDaZxkSMp1S0AlZ
0uP+/evvOOgT+9mWutfPgZolOQx1koQCKLgGeX9j6Xf3I28NubpSfAI84uTyfQrp
FnRtb79U5Hh0jMynA+U2e6niZ6UF5H41cQj9Hu+qhKBkj2IP+h96cwfnYnZFkPGR
kqZE65KyqfHPeFATwkcImQKBgCdrfhlpGiTWXCABhKQ8s+WpPLAB2ahV8XJEKyXT
UlVQuMIChGLcpnFv7P/cUxf8asx/fUY8Aj0/0CLLvulHziQjTmKj4gl86pb/oIQ3
xRRtNhU0O+/OsSfLORgIm3K6C0w0esregL/GMbJSR1TnA1gBr7/1oSnw5JC8Ab9W
injHAoGAJT1MGAiQrhlt9GCGe6Ajw4omdbY0wS9NXefnFhf7EwL0es52ezZ28zpU
2LXqSFbtann5CHgpSLxiMYPDIf+er4xgg9Bz34tz1if1rDfP2Qrxdrpr4jDnrGT3
gYC2qCpvVD9RRUMKFfnJTfl5gMQdBW/LINkHtJ82snAeLl3gjQ4=
-----END RSA PRIVATE KEY-----

View File

@ -1,74 +0,0 @@
Key Value
lease_id pki/issue/example-dot-com/d8214077-9976-8c68-9c07-6610da30aea4
lease_duration 279359999
lease_renewable false
certificate -----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIUf+jhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw
MTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS
TRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGn
SgMld6ZWRhNheZhA6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmi
YYMiIWplidMmMO5NTRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5
donyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVG
B+5+AAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABo4H1
MIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm++e
HpyM3p708bgZJuRYEdX1o+UwHwYDVR0jBBgwFoAUncSzT/6HMexyuiU9/7EgHu+o
k5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x
OjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A
AAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br
aS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy+SgMIrwfs
X1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4
aYqNKFWrRaBRAaaYZ/O1ApRTOrXqRx9Eqr0H1BXLsoAq+mWassL8sf6siae+CpwA
KqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU+idkuqfV2h1BQKgSEhFDABjFdTCN
QDAHsEHsi2M4/jRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj
xqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc/J9DIQM+Xmk=
-----END CERTIFICATE-----
issuing_ca -----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----
private_key -----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxSTRAVnygAftetT8pu
HflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGnSgMld6ZWRhNheZhA
6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmiYYMiIWplidMmMO5N
TRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5donyqtnaHuIJGuUd
y54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVGB+5+AAGF5iuHC3N2
DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABAoIBAHR7fFV0eAGaopsX
9OD0TUGlsephBXb43g0GYHfJ/1Ew18w9oaxszJEqkl+PB4W3xZ3yG3e8ZomxDOhF
RreF2WgG5xOfhDogMwu6NodbArfgnAvoC6JnW3qha8HMP4F500RFVyCRcd6A3Frd
rFtaZn/UyCsBAN8/zkwPeYHayo7xX6d9kzgRl9HluEX5PXI5+3uiBDUiM085gkLI
5Cmadh9fMdjfhDXI4x2JYmILpp/9Nlc/krB15s5n1MPNtn3yL0TI0tWp0WlwDCV7
oUm1SfIM0F1fXGFyFDcqwoIr6JCQgXk6XtTg31YhH1xgUIclUVdtHqmAwAbLdIhQ
GAiHn2kCgYEAwD4pZ8HfpiOG/EHNoWsMATc/5yC7O8F9WbvcHZQIymLY4v/7HKZb
VyOR6UQ5/O2cztSGIuKSF6+OK1C34lOyCuTSOTFrjlgEYtLIXjdGLfFdtOO8GRQR
akVXdwuzNAjTBaH5eXbG+NKcjmCvZL48dQVlfDTVulzFGbcsVTHIMQUCgYEA7IQI
FVsKnY3KqpyGqXq92LMcsT3XgW6X1BIIV+YhJ5AFUFkFrjrbXs94/8XyLfi0xBQy
efK+8g5sMs7koF8LyZEcAXWZJQduaKB71hoLlRaU4VQkL/dl2B6VFmAII/CsRCYh
r9RmDN2PF/mp98Ih9dpC1VqcCDRGoTYsd7jLalMCgYAMgH5k1wDaZxkSMp1S0AlZ
0uP+/evvOOgT+9mWutfPgZolOQx1koQCKLgGeX9j6Xf3I28NubpSfAI84uTyfQrp
FnRtb79U5Hh0jMynA+U2e6niZ6UF5H41cQj9Hu+qhKBkj2IP+h96cwfnYnZFkPGR
kqZE65KyqfHPeFATwkcImQKBgCdrfhlpGiTWXCABhKQ8s+WpPLAB2ahV8XJEKyXT
UlVQuMIChGLcpnFv7P/cUxf8asx/fUY8Aj0/0CLLvulHziQjTmKj4gl86pb/oIQ3
xRRtNhU0O+/OsSfLORgIm3K6C0w0esregL/GMbJSR1TnA1gBr7/1oSnw5JC8Ab9W
injHAoGAJT1MGAiQrhlt9GCGe6Ajw4omdbY0wS9NXefnFhf7EwL0es52ezZ28zpU
2LXqSFbtann5CHgpSLxiMYPDIf+er4xgg9Bz34tz1if1rDfP2Qrxdrpr4jDnrGT3
gYC2qCpvVD9RRUMKFfnJTfl5gMQdBW/LINkHtJ82snAeLl3gjQ4=
-----END RSA PRIVATE KEY-----
private_key_type rsa

View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBxMQowCAYDVQQDFAEqMQsw
CQYDVQQIEwJHQTELMAkGA1UEBhMCVVMxJTAjBgkqhkiG9w0BCQEWFnZpc2hhbG5h
eWFrdkBnbWFpbC5jb20xEjAQBgNVBAoTCUhhc2hpQ29ycDEOMAwGA1UECxMFVmF1
bHQwHhcNMTYwMjI5MjE0NjE2WhcNMjEwMjI3MjE0NjE2WjBxMQowCAYDVQQDFAEq
MQswCQYDVQQIEwJHQTELMAkGA1UEBhMCVVMxJTAjBgkqhkiG9w0BCQEWFnZpc2hh
bG5heWFrdkBnbWFpbC5jb20xEjAQBgNVBAoTCUhhc2hpQ29ycDEOMAwGA1UECxMF
VmF1bHQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMfRkLfIGHt1r2jjnV0N
LqRCu3oB+J1dqpM03vQt3qzIiqtuQuIA2ba7TJm2HwU3W3+rtfFcS+hkBR/LZM+u
cBPB+9b9+7i08vHjgy2P3QH/Ebxa8j1v7JtRMT2qyxWK8NlT/+wZSH82Cr812aS/
zNT56FbBo2UAtzpqeC4eiv6NAgMBAAGjQDA+MAkGA1UdEwQCMAAwCwYDVR0PBAQD
AgXgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZI
hvcNAQEFBQADggEBAG2mUwsZ6+R8qqyNjzMk7mgpsRZv9TEl6c1IiQdyjaCOPaYH
vtZpLX20um36cxrLuOUtZLllG/VJEhRZW5mXWxuOk4QunWMBXQioCDJG1ktcZAcQ
QqYv9Dzy2G9lZHjLztEac37T75RXW7OEeQREgwP11c8sQYiS9jf+7ITYL7nXjoKq
gEuH0h86BOH2O/BxgMelt9O0YCkvkLLHnE27xuNelRRZcBLSuE1GxdUi32MDJ+ff
25GUNM0zzOEaJAFE/USUBEdQqN1gvJidNXkAiMtIK7T8omQZONRaD2ZnSW8y2krh
eUg+rKis9RinqFlahLPfI5BlyQsNMEnsD07Q85E=
-----END CERTIFICATE-----

View File

@ -1,74 +0,0 @@
Key Value
lease_id pki/root/generate/exported/7bf99d76-dd3e-2c5b-04ce-5253062ad586
lease_duration 315359999
lease_renewable false
certificate -----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----
expiration 1.772072879e+09
issuing_ca -----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----
private_key -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA7FMy+FO4hFzZiHFmmY/B6A/zpyCep9PWZfGLUDtDuprHbg2p
t/TQjegMxC0AmWPZEHeG9FIJvT+WQXuLUG5v5MbG4zs21mqnYXwWAbTPaZ35bpp3
BbbquXFtAqOZG8yfrob1g9OSgmY+bG3ZNxxv35qmbBbuWyUvmPwfjzEAclMxv48w
/2Rs4dXkBuvc9PiNdQ9Sv+ZYG8GIqIcbRd38cSaXI4Q94BOHEr4jm1vqb54H7twv
0Gy9CnLXfn7ZtEDFSmG8WLk2GvIna+UC+gnxSqKCw0rcTby63rQUCgdJZF8VQUVi
18+BMNLXI4pT/P9cxTaCJC/YeuV5a3SaniOoFQIDAQABAoIBAQCoGZJC84JnnIgb
ttZNWuWKBXbCJcDVDikOQJ9hBZbqsFg1X0CfGmQS3MHf9Ubc1Ro8zVjQh15oIEfn
8lIpdzTeXcpxLdiW8ix3ekVJF20F6pnXY8ZP6UnTeOwamXY6QPZAtb0D9UXcvY+f
nw+IVRD6082XS0Rmzu+peYWVXDy+FDN+HJRANBcdJZz8gOmNBIe0qDWx1b85d/s8
2Kk1Wwdss1IwAGeSddTSwzBNaaHdItZaMZOqPW1gRyBfVSkcUQIE6zn2RKw2b70t
grkIvyRcTdfmiKbqkkJ+eR+ITOUt0cBZSH4cDjlQA+r7hulvoBpQBRj068Toxkcc
bTagHaPBAoGBAPWPGVkHqhTbJ/DjmqDIStxby2M1fhhHt4xUGHinhUYjQjGOtDQ9
0mfaB7HObudRiSLydRAVGAHGyNJdQcTeFxeQbovwGiYKfZSA1IGpea7dTxPpGEdN
ksA0pzSp9MfKzX/MdLuAkEtO58aAg5YzsgX9hDNxo4MhH/gremZhEGZlAoGBAPZf
lqdYvAL0fjHGJ1FUEalhzGCGE9PH2iOqsxqLCXK7bDbzYSjvuiHkhYJHAOgVdiW1
lB34UHHYAqZ1VVoFqJ05gax6DE2+r7K5VV3FUCaC0Zm3pavxchU9R/TKP82xRrBj
AFWwdgDTxUyvQEmgPR9sqorftO71Iz2tiwyTpIfxAoGBAIhEMLzHFAse0rtKkrRG
ccR27BbRyHeQ1Lp6sFnEHKEfT8xQdI/I/snCpCJ3e/PBu2g5Q9z416mktiyGs8ib
thTNgYsGYnxZtfaCx2pssanoBcn2wBJRae5fSapf5gY49HDG9MBYR7qCvvvYtSzU
4yWP2ZzyotpRt3vwJKxLkN5BAoGAORHpZvhiDNkvxj3da7Rqpu7VleJZA2y+9hYb
iOF+HcqWhaAY+I+XcTRrTMM/zYLzLEcEeXDEyao86uwxCjpXVZw1kotvAC9UqbTO
tnr3VwRkoxPsV4kFYTAh0+1pnC8dbcxxDmhi3Uww3tOVs7hfkEDuvF6XnebA9A+Y
LyCgMzECgYEA6cCU8QODOivIKWFRXucvWckgE6MYDBaAwe6qcLsd1Q/gpE2e3yQc
4RB3bcyiPROLzMLlXFxf1vSNJQdIaVfrRv+zJeGIiivLPU8+Eq4Lrb+tl1LepcOX
OzQeADTSCn5VidOfjDkIst9UXjMlrFfV9/oJEw5Eiqa6lkNPCGDhfA8=
-----END RSA PRIVATE KEY-----
private_key_type rsa
serial_number 6f:98:9d:f8:67:1a:31:e3:27:60:1b:f7:32:f7:53:19:68:a0:c8:9d

View File

@ -1,12 +0,0 @@
-----BEGIN X509 CRL-----
MIIBrjCBlzANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbRcN
MTYwMjI5MDIyOTE3WhcNMjUwMTA1MTAyOTE3WjArMCkCFG+YnfhnGjHjJ2Ab9zL3
UxlooMidFxExNjAyMjgyMTI5MTctMDUwMKAjMCEwHwYDVR0jBBgwFoAUncSzT/6H
MexyuiU9/7EgHu+ok5swDQYJKoZIhvcNAQELBQADggEBAG9YDXpNe4LJroKZmVCn
HqMhW8eyzyaPak2nPPGCVUnc6vt8rlBYQU+xlBizD6xatZQDMPgrT8sBl9W3ysXk
RUlliHsT/SHddMz5dAZsBPRMJ7pYWLTx8jI4w2WRfbSyI4bY/6qTRNkEBUv+Fk8J
xvwB89+EM0ENcVMhv9ghsUA8h7kOg673HKwRstLDAzxS/uLmEzFjj8SV2m5DbV2Y
UUCKRSV20/kxJMIC9x2KikZhwOSyv1UE1otD+RQvbfAoZPUDmvp2FR/E0NGjBBOg
1TtCPRrl63cjqU3s8KQ4uah9Vj+Cwcu9n/yIKKtNQq4NKHvagv8GlUsoJ4BdAxCw
IA0=
-----END X509 CRL-----

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw
MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7
Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0
z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x
AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb
6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH
SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx
7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc
BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2
U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa
cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N
ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ
t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk
zehNe5dFTjFpylg1o6b8Ow==
-----END CERTIFICATE-----

View File

@ -1 +0,0 @@
92223EAFBBEE17AF

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA7FMy+FO4hFzZiHFmmY/B6A/zpyCep9PWZfGLUDtDuprHbg2p
t/TQjegMxC0AmWPZEHeG9FIJvT+WQXuLUG5v5MbG4zs21mqnYXwWAbTPaZ35bpp3
BbbquXFtAqOZG8yfrob1g9OSgmY+bG3ZNxxv35qmbBbuWyUvmPwfjzEAclMxv48w
/2Rs4dXkBuvc9PiNdQ9Sv+ZYG8GIqIcbRd38cSaXI4Q94BOHEr4jm1vqb54H7twv
0Gy9CnLXfn7ZtEDFSmG8WLk2GvIna+UC+gnxSqKCw0rcTby63rQUCgdJZF8VQUVi
18+BMNLXI4pT/P9cxTaCJC/YeuV5a3SaniOoFQIDAQABAoIBAQCoGZJC84JnnIgb
ttZNWuWKBXbCJcDVDikOQJ9hBZbqsFg1X0CfGmQS3MHf9Ubc1Ro8zVjQh15oIEfn
8lIpdzTeXcpxLdiW8ix3ekVJF20F6pnXY8ZP6UnTeOwamXY6QPZAtb0D9UXcvY+f
nw+IVRD6082XS0Rmzu+peYWVXDy+FDN+HJRANBcdJZz8gOmNBIe0qDWx1b85d/s8
2Kk1Wwdss1IwAGeSddTSwzBNaaHdItZaMZOqPW1gRyBfVSkcUQIE6zn2RKw2b70t
grkIvyRcTdfmiKbqkkJ+eR+ITOUt0cBZSH4cDjlQA+r7hulvoBpQBRj068Toxkcc
bTagHaPBAoGBAPWPGVkHqhTbJ/DjmqDIStxby2M1fhhHt4xUGHinhUYjQjGOtDQ9
0mfaB7HObudRiSLydRAVGAHGyNJdQcTeFxeQbovwGiYKfZSA1IGpea7dTxPpGEdN
ksA0pzSp9MfKzX/MdLuAkEtO58aAg5YzsgX9hDNxo4MhH/gremZhEGZlAoGBAPZf
lqdYvAL0fjHGJ1FUEalhzGCGE9PH2iOqsxqLCXK7bDbzYSjvuiHkhYJHAOgVdiW1
lB34UHHYAqZ1VVoFqJ05gax6DE2+r7K5VV3FUCaC0Zm3pavxchU9R/TKP82xRrBj
AFWwdgDTxUyvQEmgPR9sqorftO71Iz2tiwyTpIfxAoGBAIhEMLzHFAse0rtKkrRG
ccR27BbRyHeQ1Lp6sFnEHKEfT8xQdI/I/snCpCJ3e/PBu2g5Q9z416mktiyGs8ib
thTNgYsGYnxZtfaCx2pssanoBcn2wBJRae5fSapf5gY49HDG9MBYR7qCvvvYtSzU
4yWP2ZzyotpRt3vwJKxLkN5BAoGAORHpZvhiDNkvxj3da7Rqpu7VleJZA2y+9hYb
iOF+HcqWhaAY+I+XcTRrTMM/zYLzLEcEeXDEyao86uwxCjpXVZw1kotvAC9UqbTO
tnr3VwRkoxPsV4kFYTAh0+1pnC8dbcxxDmhi3Uww3tOVs7hfkEDuvF6XnebA9A+Y
LyCgMzECgYEA6cCU8QODOivIKWFRXucvWckgE6MYDBaAwe6qcLsd1Q/gpE2e3yQc
4RB3bcyiPROLzMLlXFxf1vSNJQdIaVfrRv+zJeGIiivLPU8+Eq4Lrb+tl1LepcOX
OzQeADTSCn5VidOfjDkIst9UXjMlrFfV9/oJEw5Eiqa6lkNPCGDhfA8=
-----END RSA PRIVATE KEY-----

View File

@ -1,16 +0,0 @@
[ req ]
default_bits = 2048
encrypt_key = no
prompt = no
default_md = sha256
req_extensions = req_v3
distinguished_name = dn
[ dn ]
CN = example.com
[ req_v3 ]
2.1.1.1=ASN1:UTF8String:A UTF8String Extension
2.1.1.2=ASN1:UTF8:A UTF8 Extension
2.1.1.3=ASN1:IA5:An IA5 Extension
2.1.1.4=ASN1:VISIBLE:A Visible Extension

View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDAzCCAesCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDM2PrLyK/wVQIcnK362ZylDrIVMjFQzps/0AxM
ke+8MNPMArBlSAhnZus6qb0nN0nJrDLkHQgYqnSvK9N7VUv/xFblEcOLBlciLhyN
Wkm92+q/M/xOvUVmnYkN3XgTI5QNxF7ZWDFHmwCNV27RraQZou0hG7yvyoILLMQE
3MnMCNM1nZ9JIuBMcRsZLGqQ1XNaQljboRVIUjimzkcfYyTruhLosTIbwForp78J
MzHHqVjtLJXPqUnRMS7KhGMj1f2mIswQzCv6F2PWEzNBbP4Gb67znKikKDs0RgyL
RyfizFNFJSC58XntK8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGggacwgaQG
CSqGSIb3DQEJDjGBljCBkzAcBgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATAf
BgNRAQEEGAwWQSBVVEY4U3RyaW5nIEV4dGVuc2lvbjAZBgNRAQIEEgwQQSBVVEY4
IEV4dGVuc2lvbjAZBgNRAQMEEhYQQW4gSUE1IEV4dGVuc2lvbjAcBgNRAQQEFRoT
QSBWaXNpYmxlIEV4dGVuc2lvbjANBgkqhkiG9w0BAQsFAAOCAQEAtYjewBcqAXxk
tDY0lpZid6ZvfngdDlDZX0vrs3zNppKNe5Sl+jsoDOexqTA7HQA/y1ru117sAEeB
yiqMeZ7oPk8b3w+BZUpab7p2qPMhZypKl93y/jGXGscc3jRbUBnym9S91PSq6wUd
f2aigSqFc9+ywFVdx5PnnZUfcrUQ2a+AweYEkGOzXX2Ga+Ige8grDMCzRgCoP5cW
kM5ghwZp5wYIBGrKBU9iDcBlmnNhYaGWf+dD00JtVDPNn2bJnCsJHIO0nklZgnrS
fli8VQ1nYPkONdkiRYLt6//6at1iNDoDgsVCChtlVkLpxFIKcDFUHlffZsc1kMFI
HTX579k8hA==
-----END CERTIFICATE REQUEST-----

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDRjCCAi6gAwIBAgIJAJIiPq+77hejMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
BAMTC2V4YW1wbGUuY29tMB4XDTE3MTEyOTE5MTgwM1oXDTI3MTEyNzE5MTgwM1ow
FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDM2PrLyK/wVQIcnK362ZylDrIVMjFQzps/0AxMke+8MNPMArBlSAhn
Zus6qb0nN0nJrDLkHQgYqnSvK9N7VUv/xFblEcOLBlciLhyNWkm92+q/M/xOvUVm
nYkN3XgTI5QNxF7ZWDFHmwCNV27RraQZou0hG7yvyoILLMQE3MnMCNM1nZ9JIuBM
cRsZLGqQ1XNaQljboRVIUjimzkcfYyTruhLosTIbwForp78JMzHHqVjtLJXPqUnR
MS7KhGMj1f2mIswQzCv6F2PWEzNBbP4Gb67znKikKDs0RgyLRyfizFNFJSC58Xnt
K8jwHK1D8W3UepFf4K8xNFnhPoKWtWfJAgMBAAGjgZYwgZMwHAYDVR0RBBUwE4IL
ZXhhbXBsZS5jb22HBH8AAAEwHwYDUQEBBBgMFkEgVVRGOFN0cmluZyBFeHRlbnNp
b24wGQYDUQECBBIMEEEgVVRGOCBFeHRlbnNpb24wGQYDUQEDBBIWEEFuIElBNSBF
eHRlbnNpb24wHAYDUQEEBBUaE0EgVmlzaWJsZSBFeHRlbnNpb24wDQYJKoZIhvcN
AQELBQADggEBAGU/iA6saupEaGn/veVNCknFGDL7pst5D6eX/y9atXlBOdJe7ZJJ
XQRkeHJldA0khVpzH7Ryfi+/25WDuNz+XTZqmb4ppeV8g9amtqBwxziQ9UUwYrza
eDBqdXBaYp/iHUEHoceX4F44xuo80BIqwF0lD9TFNUFoILnF26ajhKX0xkGaiKTH
6SbjBfHoQVMzOHokVRWregmgNycV+MAI9Ne9XkIZvdOYeNlcS9drZeJI3szkiaxB
WWaWaAr5UU2Z0yUCZnAIDMRcIiUbSEjIDz504sSuCzTctMOxWZu0r/0UrXRzwZZi
HAaKm3MUmBh733ChP4rTB58nr5DEr5rJ9P8=
-----END CERTIFICATE-----

View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDM2PrLyK/wVQIc
nK362ZylDrIVMjFQzps/0AxMke+8MNPMArBlSAhnZus6qb0nN0nJrDLkHQgYqnSv
K9N7VUv/xFblEcOLBlciLhyNWkm92+q/M/xOvUVmnYkN3XgTI5QNxF7ZWDFHmwCN
V27RraQZou0hG7yvyoILLMQE3MnMCNM1nZ9JIuBMcRsZLGqQ1XNaQljboRVIUjim
zkcfYyTruhLosTIbwForp78JMzHHqVjtLJXPqUnRMS7KhGMj1f2mIswQzCv6F2PW
EzNBbP4Gb67znKikKDs0RgyLRyfizFNFJSC58XntK8jwHK1D8W3UepFf4K8xNFnh
PoKWtWfJAgMBAAECggEAW7hLkzMok9N8PpNo0wjcuor58cOnkSbxHIFrAF3XmcvD
CXWqxa6bFLFgYcPejdCTmVkg8EKPfXvVAxn8dxyaCss+nRJ3G6ibGxLKdgAXRItT
cIk2T4svp+KhmzOur+MeR4vFbEuwxP8CIEclt3yoHVJ2Gnzw30UtNRO2MPcq48/C
ZODGeBqUif1EGjDAvlqu5kl/pcDBJ3ctIZdVUMYYW4R9JtzKsmwhX7CRCBm8k5hG
2uzn8AKwpuVtfWcnX59UUmHGJ8mjETuNLARRAwWBWhl8f7wckmi+PKERJGEM2QE5
/Voy0p22zmQ3waS8LgiI7YHCAEFqjVWNziVGdR36gQKBgQDxkpfkEsfa5PieIaaF
iQOO0rrjEJ9MBOQqmTDeclmDPNkM9qvCF/dqpJfOtliYFxd7JJ3OR2wKrBb5vGHt
qIB51Rnm9aDTM4OUEhnhvbPlERD0W+yWYXWRvqyHz0GYwEFGQ83h95GC/qfTosqy
LEzYLDafiPeNP+DG/HYRljAxUwKBgQDZFOWHEcZkSFPLNZiksHqs90OR2zIFxZcx
SrbkjqXjRjehWEAwgpvQ/quSBxrE2E8xXgVm90G1JpWzxjUfKKQRM6solQeEpnwY
kCy2Ozij/TtbLNRlU65UQ+nMto8KTSIyJbxxdOZxYdtJAJQp1FJO1a1WC11z4+zh
lnLV1O5S8wKBgQCDf/QU4DBQtNGtas315Oa96XJ4RkUgoYz+r1NN09tsOERC7UgE
KP2y3JQSn2pMqE1M6FrKvlBO4uzC10xLja0aJOmrssvwDBu1D8FtA9IYgJjFHAEG
v1i7lJrgdu7TUtx1flVli1l3gF4lM3m5UaonBrJZV7rB9iLKzwUKf8IOJwKBgFt/
QktPA6brEV56Za8sr1hOFA3bLNdf9B0Tl8j4ExWbWAFKeCu6MUDCxsAS/IZxgdeW
AILovqpC7CBM78EFWTni5EaDohqYLYAQ7LeWeIYuSyFf4Nogjj74LQha/iliX4Jx
g17y3dp2W34Gn2yOEG8oAxpcSfR54jMnPZnBWP5fAoGBAMNAd3oa/xq9A5v719ik
naD7PdrjBdhnPk4egzMDv54y6pCFlvFbEiBduBWTmiVa7dSzhYtmEbri2WrgARlu
vkfTnVH9E8Hnm4HTbNn+ebxrofq1AOAvdApSoslsOP1NT9J6zB89RzChJyzjbIQR
Gevrutb4uO9qpB1jDVoMmGde
-----END PRIVATE KEY-----

View File

@ -1,18 +0,0 @@
[ req ]
default_bits = 2048
encrypt_key = no
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_v3
[ req_v3 ]
subjectAltName = @alt_names
[ dn ]
CN = example.com
OU = engineering
[ alt_names ]
IP.1 = 127.0.0.1
email = valid@example.com

View File

@ -1,17 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICpjCCAY4CAQAwLDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xFDASBgNVBAsMC2Vu
Z2luZWVyaW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsI4ZJWfQ
mI/3qfacas7O260Iii06oTP4GoQ5QpAYvcfWKKnkXagd0fBl+hfpnrK6ojYY71Jt
cMstVdff2Wc5D3bnQ8Hikb1TMhdAAtZDUW4QbeWAXJ4mkDq1ARRcbTvK121bmDQp
1efepohe0mDxNCruGSHpqfayC6LOkk7XZ73VAOcPPV5OOpY8el7quUdfvElxn0vH
KBVlFRBBW2fbY5EAHDMkmBjWr0ofpwb+vhSuQlOZgsbd20mjDwSYIbywG0tAEOoj
pLI0pOQV5msdfbqmKYE6ZmUeL/Q/pZjYh5uxFUZ4aMD/STDaeq7GdYQYcm17WL+N
ceal9+gKceJSiQIDAQABoDUwMwYJKoZIhvcNAQkOMSYwJDAiBgNVHREEGzAZhwR/
AAABgRF2YWxpZEBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAf1tnXgX1
/1p2MAxHhcil5/lsOMgHWU5dRL6KjK2cepuBpfzlCbxFtvnsj9WHx46f9Q/xbqy+
1A2TJIBUWxK+Eji//WJxbDsi7fmV5VQlpG7+sEa7yin3KobfMd84nDIYP8wLF1Fq
HhRf7ZjIDh3zTgBosvIIjGEyABrouGYm4Nl409I09MftGXK/5TLJkgm6sxcJCAHG
BMm8IFaI0VN5QFIHKvJ/1oQLpLV+gvtR6jAM/99LXc0SXmFn0Jcy/mE/hxJXJigW
dDOblgjliJo0rWwHK4gfsgpMbHjJiG70g0XHtTpBW+i/NyuPnc8RYzBIJv+4sks+
hWSmn6/IL46qTg==
-----END CERTIFICATE REQUEST-----

View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDATCCAemgAwIBAgIJAMAMmdiZi5G/MA0GCSqGSIb3DQEBCwUAMCwxFDASBgNV
BAMMC2V4YW1wbGUuY29tMRQwEgYDVQQLDAtlbmdpbmVlcmluZzAeFw0xODA5MDEx
NDM0NTVaFw0yODA4MjkxNDM0NTVaMCwxFDASBgNVBAMMC2V4YW1wbGUuY29tMRQw
EgYDVQQLDAtlbmdpbmVlcmluZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALCOGSVn0JiP96n2nGrOztutCIotOqEz+BqEOUKQGL3H1iip5F2oHdHwZfoX
6Z6yuqI2GO9SbXDLLVXX39lnOQ9250PB4pG9UzIXQALWQ1FuEG3lgFyeJpA6tQEU
XG07ytdtW5g0KdXn3qaIXtJg8TQq7hkh6an2sguizpJO12e91QDnDz1eTjqWPHpe
6rlHX7xJcZ9LxygVZRUQQVtn22ORABwzJJgY1q9KH6cG/r4UrkJTmYLG3dtJow8E
mCG8sBtLQBDqI6SyNKTkFeZrHX26pimBOmZlHi/0P6WY2IebsRVGeGjA/0kw2nqu
xnWEGHJte1i/jXHmpffoCnHiUokCAwEAAaMmMCQwIgYDVR0RBBswGYcEfwAAAYER
dmFsaWRAZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAHATSjW20P7+6en0
Oq/n/R/i+aCgzcxIWSgf3dhOyxGfBW6svSg8ZtBQFEZZHqIRSXZX89zz25+mvwqi
kGRJKKzD/KDd2v9C5+H3DSuu9CqClVtpjF2XLvRHnuclBIrwvyijRcqa2GCTA9YZ
sOfVVGQYobDbtRCgTwWkEpU9RrZWWoD8HAYMkxFc1Cs/vJconeAaQDPEIZx9wnAN
4r/F5143rn5dyhbYehz1/gykL3K0v7s4U5NhaSACE2AiQ+63vhAEd5xt9WPKAAGY
zEyK4b/qPO88mxLr3A/rdzzt1UYAwT38kXA7aV82AH1J8EaCr7tLnXzyLXiEsI4E
BOrHBgU=
-----END CERTIFICATE-----

View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwjhklZ9CYj/ep
9pxqzs7brQiKLTqhM/gahDlCkBi9x9YoqeRdqB3R8GX6F+mesrqiNhjvUm1wyy1V
19/ZZzkPdudDweKRvVMyF0AC1kNRbhBt5YBcniaQOrUBFFxtO8rXbVuYNCnV596m
iF7SYPE0Ku4ZIemp9rILos6STtdnvdUA5w89Xk46ljx6Xuq5R1+8SXGfS8coFWUV
EEFbZ9tjkQAcMySYGNavSh+nBv6+FK5CU5mCxt3bSaMPBJghvLAbS0AQ6iOksjSk
5BXmax19uqYpgTpmZR4v9D+lmNiHm7EVRnhowP9JMNp6rsZ1hBhybXtYv41x5qX3
6Apx4lKJAgMBAAECggEAF1Jd7fv9qPlzfKcP2GgDGS+NLjt1QDAOOOp4aduA+Si5
mFuAyAJaFg5MWjHocUcosh61Qn+/5yNflLRUZHJnLizFtcSZuiipIbfCg91rvQjt
8KZdQ168t1aZ7E+VOfSpAbX3YG6bjB754UOoSt/1XK/DDdzV8dadhD34TYlOmOxZ
MMnIRERqa+IBSn90TONWPyY3ELSpaiCkz1YZpp6g9RnTACZKLwzBMSunNO5qbEfH
TWlk5o14DZ3zRu5gLT5wy3SGfzm2M+qi8afQq1MT2I6opXj4KU3c64agjNUBYTq7
S2YWmw6yrqPzxcg0hOz9H6djCx2oen/UxM2z4uoE1QKBgQDlHIFQcVTWEmxhy5yp
uV7Ya5ubx6rW4FnCgh5lJ+wWuSa5TkMuBr30peJn0G6y0I0J1El4o3iwLD/jxwHb
BIJTB1z5fBo3K7lhpZLuRFSWe9Mcd/Aj2pFcy5TqaIV9x8bgVAMVOoZAq9muiEog
zIWVWrVF6FDuFgRMRegNDej6pwKBgQDFRpNQMscPpH+x6xeS0E8boZKnHyuJUZQZ
kfEmnHQuTYmmHS4kXSnJhjODa53YddknTrPrHOvddDDYAaulyyYitPemubYQzBog
MyIgaeFSw/eHrcr/8g4QTohRFcI71xnKRmHvQZb8UflFJkqsqil6WZ6FJiC+STcn
Qdnhol9fTwKBgQCZtGDw1cdjgqKhjVcB6nG94ZtYjECJvaOaQW8g0AKsT/SxttaN
B0ri2XMl0IijgBROttO/knQCRP1r03PkOocwKq1uVprDzpqk7s6++KqC9nlwDOrX
Muf4iD/UbuC3vJIop1QWJtgwhNoaJCcPEAbCZ0Nbrfq1b6Hchb2jHGTj2wKBgHJo
DpDJEeaBeMi+1SoAgpA8sKcZDY+SbvgxShAhVcNwli5u586Q9OX5XTCPHbhmB+yi
2Pa2DBefBaCPv3LkEJa6KpFXTD4Lj+8ymE0B+nmcSpY19O9f+kX8tVOI8d7wTPWg
wbUWbbCg/ZXbshzWhj19cdA4H28bWM/8gZY4K2VDAoGBAMYsNhKdu9ON/7vaLijh
kai2tQLObYqDV6OAzdYm1gopmTTLcxQ6jP6aQlyw1ie51ms/hFozmNkGkaQGD8pp
751Lv3prQz/lDaZeQfKANNN1tpz/QqUOu2di9secMmodxXkwcLzcEKjWPDTuPhcO
VODU1hC5oj8yGFInoDLL2B0K
-----END PRIVATE KEY-----

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDPjCCAiagAwIBAgIUfIKsF2VPT7sdFcKOHJH2Ii6K4MwwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYwNTQyWhgPMjA2
NjA0MjAxNjA2MTJaMBYxFDASBgNVBAMTC215dmF1bHQuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOimEXawD2qBoLCFP3Skq5zi1XzzcMAJlfdS
xz9hfymuJb+cN8rB91HOdU9wQCwVKnkUtGWxUnMp0tT0uAZj5NzhNfyinf0JGAbP
67HDzVZhGBHlHTjPX0638yaiUx90cTnucX0N20SgCYct29dMSgcPl+W78D3Jw3xE
JsHQPYS9ASe2eONxG09F/qNw7w/RO5/6WYoV2EmdarMMxq52pPe2chtNMQdSyOUb
cCcIZyk4QVFZ1ZLl6jTnUPb+JoCx1uMxXvMek4NF/5IL0Wr9dw2gKXKVKoHDr6SY
WrCONRw61A5Zwx1V+kn73YX3USRlkufQv/ih6/xThYDAXDC9cwIDAQABo4GBMH8w
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOuKvPiU
G06iHkRXAOeMiUdBfHFyMB8GA1UdIwQYMBaAFOuKvPiUG06iHkRXAOeMiUdBfHFy
MBwGA1UdEQQVMBOCC215dmF1bHQuY29thwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB
AQBcN/UdAMzc7UjRdnIpZvO+5keBGhL/vjltnGM1dMWYHa60Y5oh7UIXF+P1RdNW
n7g80lOyvkSR15/r1rDkqOK8/4oruXU31EcwGhDOC4hU6yMUy4ltV/nBoodHBXNh
MfKiXeOstH1vdI6G0P6W93Bcww6RyV1KH6sT2dbETCw+iq2VN9CrruGIWzd67UT/
spe/kYttr3UYVV3O9kqgffVVgVXg/JoRZ3J7Hy2UEXfh9UtWNanDlRuXaZgE9s/d
CpA30CHpNXvKeyNeW2ktv+2nAbSpvNW+e6MecBCTBIoDSkgU8ShbrzmDKVwNN66Q
5gn6KxUPBKHEtNzs5DgGM7nq
-----END CERTIFICATE-----

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDPjCCAiagAwIBAgIUJfHFxtLQBOkjY9ivHx0AIsRDcH0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYxMjI5WhgPMjA2
NjA0MjAxNjEyNTlaMBYxFDASBgNVBAMTC215dmF1bHQuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqj8ANjAGrg5BgUb3owGwUHlMYDxljMdwroA/
Bv76ESjomj1zCyVtoJxlDZ8m9VcKQldk5ashFNuY+Ms9FrJ1YsePvsfStNe37C26
2uldDToh5rm7K8uwp/bQiErwM9QZMCVYCPEH8QgETPg9qWnikDFLMqcLBNbIiXVL
alxEYgA1Qt6+ayMvoS35288hFdZj6a0pCF0+zMHORZxloPhkXWnZLp5lWBiunSJG
0kVz56TjF+oY0L74iW4y3x2805biisGvFqgpZJW8/hLw/kDthNylNTzEqBktsctQ
BXpSMcwG3woJ0uZ8cH/HA/m0VDeIA77UisXnlLiQDpdB7U7QPwIDAQABo4GBMH8w
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMLETWAs
OFNsKJ+uqzChCZvIpxX4MB8GA1UdIwQYMBaAFMLETWAsOFNsKJ+uqzChCZvIpxX4
MBwGA1UdEQQVMBOCC215dmF1bHQuY29thwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB
AQCRlFb6bZDrq3NkoZF9evls7cT41V3XCdykMA4K9YRgDroZ5psanSvYEnSrk9cU
Y7sVYW7b8qSRWkLZrHCAwc2V0/i5F5j4q9yVnWaTZ+kOVCFYCI8yUS7ixRQdTLNN
os/r9dcRSzzTEqoQThAzn571yRcbJHzTjda3gCJ5F4utYUBU2F9WK+ukW9nqfepa
ju5vEEGDuL2+RyApzL0nGzMUkCdBcK82QBksTlElPnbICbJZWUUMTZWPaZ7WGDDa
Pj+pWMXiDQmzIuzgXUCNtQL6lEv4tQwGYRHjjPmhgJP4sr6Cyrj4G0iljrqM+z/3
gLyJOlNU8c5x02/C1nFDDa14
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuOimEXawD2qBoLCFP3Skq5zi1XzzcMAJlfdSxz9hfymuJb+c
N8rB91HOdU9wQCwVKnkUtGWxUnMp0tT0uAZj5NzhNfyinf0JGAbP67HDzVZhGBHl
HTjPX0638yaiUx90cTnucX0N20SgCYct29dMSgcPl+W78D3Jw3xEJsHQPYS9ASe2
eONxG09F/qNw7w/RO5/6WYoV2EmdarMMxq52pPe2chtNMQdSyOUbcCcIZyk4QVFZ
1ZLl6jTnUPb+JoCx1uMxXvMek4NF/5IL0Wr9dw2gKXKVKoHDr6SYWrCONRw61A5Z
wx1V+kn73YX3USRlkufQv/ih6/xThYDAXDC9cwIDAQABAoIBAG3bCo7ljMQb6tel
CAUjL5Ilqz5a9ebOsONABRYLOclq4ePbatxawdJF7/sSLwZxKkIJnZtvr2Hkubxg
eOO8KC0YbVS9u39Rjc2QfobxHfsojpbWSuCJl+pvwinbkiUAUxXR7S/PtCPJKat/
fGdYCiMQ/tqnynh4vR4+/d5o12c0KuuQ22/MdEf3GOadUamRXS1ET9iJWqla1pJW
TmzrlkGAEnR5PPO2RMxbnZCYmj3dArxWAnB57W+bWYla0DstkDKtwg2j2ikNZpXB
nkZJJpxR76IYD1GxfwftqAKxujKcyfqB0dIKCJ0UmfOkauNWjexroNLwaAOC3Nud
XIxppAECgYEA1wJ9EH6A6CrSjdzUocF9LtQy1LCDHbdiQFHxM5/zZqIxraJZ8Gzh
Q0d8JeOjwPdG4zL9pHcWS7+x64Wmfn0+Qfh6/47Vy3v90PIL0AeZYshrVZyJ/s6X
YkgFK80KEuWtacqIZ1K2UJyCw81u/ynIl2doRsIbgkbNeN0opjmqVTMCgYEA3CkW
2fETWK1LvmgKFjG1TjOotVRIOUfy4iN0kznPm6DK2PgTF5DX5RfktlmA8i8WPmB7
YFOEdAWHf+RtoM/URa7EAGZncCWe6uggAcWqznTS619BJ63OmncpSWov5Byg90gJ
48qIMY4wDjE85ypz1bmBc2Iph974dtWeDtB7dsECgYAyKZh4EquMfwEkq9LH8lZ8
aHF7gbr1YeWAUB3QB49H8KtacTg+iYh8o97pEBUSXh6hvzHB/y6qeYzPAB16AUpX
Jdu8Z9ylXsY2y2HKJRu6GjxAewcO9bAH8/mQ4INrKT6uIdx1Dq0OXZV8jR9KVLtB
55RCfeLhIBesDR0Auw9sVQKBgB0xTZhkgP43LF35Ca1btgDClNJGdLUztx8JOIH1
HnQyY/NVIaL0T8xO2MLdJ131pGts+68QI/YGbaslrOuv4yPCQrcS3RBfzKy1Ttkt
TrLFhtoy7T7HqyeMOWtEq0kCCs3/PWB5EIoRoomfOcYlOOrUCDg2ge9EP4nyVVz9
hAGBAoGBAJXw/ufevxpBJJMSyULmVWYr34GwLC1OhSE6AVVt9JkIYnc5L4xBKTHP
QNKKJLmFmMsEqfxHUNWmpiHkm2E0p37Zehui3kywo+A4ybHPTua70ZWQfZhKxLUr
PvJa8JmwiCM7kO8zjOv+edY1mMWrbjAZH1YUbfcTHmST7S8vp0F3
-----END RSA PRIVATE KEY-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqj8ANjAGrg5BgUb3owGwUHlMYDxljMdwroA/Bv76ESjomj1z
CyVtoJxlDZ8m9VcKQldk5ashFNuY+Ms9FrJ1YsePvsfStNe37C262uldDToh5rm7
K8uwp/bQiErwM9QZMCVYCPEH8QgETPg9qWnikDFLMqcLBNbIiXVLalxEYgA1Qt6+
ayMvoS35288hFdZj6a0pCF0+zMHORZxloPhkXWnZLp5lWBiunSJG0kVz56TjF+oY
0L74iW4y3x2805biisGvFqgpZJW8/hLw/kDthNylNTzEqBktsctQBXpSMcwG3woJ
0uZ8cH/HA/m0VDeIA77UisXnlLiQDpdB7U7QPwIDAQABAoIBADivQ2XHdeHsUzk1
JOz8efVBfgGo+nL2UPl5MAMnUKH4CgKZJT3311mb2TXA4RrdQUg3ixvBcAFe4L8u
BIgTIWyjX6Q5KloWXWHhFA8hll76FSGag8ygRJCYaHSI5xOKslxKgtZvUqKZdb0f
BoDrBYnXL9+MqOmSjjDegh7G2+n49n774Z2VVR47TZTBB5LCWDWj4AtEcalgwlvw
d5yL/GU/RfCkXCjCeie1pInp3eCMUI9jlvbe/vyaoFq2RiaJw1LSlJLXZBMYzaij
XkgMtRsr5bf0Tg2z3SPiaa9QZogfVLqHWAt6RHZf9Keidtiho+Ad6/dzJu+jKDys
Z6cthOECgYEAxMUCIYKO74BtPRN2r7KxbSjHzFsasxbfwkSg4Qefd4UoZJX2ShlL
cClnef3WdkKxtShJhqEPaKTYTrfgM+iz/a9+3lAFnS4EZawSf3YgXXslVTory0Da
yPQZKxX6XsupaLl4s13ehw/D0qfdxWVYaiFad3ePEE4ytmSkMMHLHo8CgYEA3X4a
jMWVbVv1W1lj+LFcg7AhU7lHgla+p7NI4gHw9V783noafnW7/8pNF80kshYo4u0g
aJRwaU/Inr5uw14eAyEjB4X7N8AE5wGmcxxS2uluGG6r3oyQSJBqktGnLwyTfcfC
XrfsGJza2BRGF4Mn8SFb7WtCl3f1qu0hTF+mC1ECgYB4oA1eXZsiV6if+H6Z1wHN
2WIidPc5MpyZi1jUmse3jXnlr8j8Q+VrLPayYlpGxTwLwlbQoYvAqs2v9CkNqWot
6pfr0UKfyMYJTiNI4DGXHRcV2ENgprF436tOLnr+AfwopwrHapQwWAnD6gSaLja1
WR0Mf87EQCv2hFvjR+otIQKBgQCLyvJQ1MeZzQdPT1zkcnSUfM6b+/1hCwSr7WDb
nCQLiZcJh4E/PWmZaII9unEloQzPJKBmwQEtxng1kLVxwu4oRXrJXcuPhTbS4dy/
HCpDFj8xVnBNNuQ9mEBbR80/ya0xHqnThDuT0TPiWvFeF55W9xoA/8h4tvKrnZx9
ioTO8QKBgCMqRa5pHb+vCniTWUTz9JZRnRsdq7fRSsJHngMe5gOR4HylyAmmqKrd
kEXfkdu9TH2jxSWcZbHUPVwKfOUqQUZMz0pml0DIs1kedUDFanTZ8Rgg5SGUHBW0
5bNCq64tKMmw6GiicaAGqd04OPo85WD9h8mPhM1Jdv/UmTV+HFAr
-----END RSA PRIVATE KEY-----

View File

@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIUBLqh6ctGWVDUxFhxJX7m6S/bnrcwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYwOTI2WhgPMjA2
NjA0MjAxNTA5NTZaMBsxGTAXBgNVBAMTEGNlcnQubXl2YXVsdC5jb20wggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY3gPB29kkdbu0mPO6J0efagQhSiXB
9OyDuLf5sMk6CVDWVWal5hISkyBmw/lXgF7qC2XFKivpJOrcGQd5Ep9otBqyJLzI
b0IWdXuPIrVnXDwcdWr86ybX2iC42zKWfbXgjzGijeAVpl0UJLKBj+fk5q6NvkRL
5FUL6TRV7Krn9mrmnrV9J5IqV15pTd9W2aVJ6IqWvIPCACtZKulqWn4707uy2X2W
1Stq/5qnp1pDshiGk1VPyxCwQ6yw3iEcgecbYo3vQfhWcv7Q8LpSIM9ZYpXu6OmF
+czqRZS9gERl+wipmmrN1MdYVrTuQem21C/PNZ4jo4XUk1SFx6JrcA+lAgMBAAGj
gfUwgfIwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSe
Cl9WV3BjGCwmS/KrDSLRjfwyqjAfBgNVHSMEGDAWgBTrirz4lBtOoh5EVwDnjIlH
QXxxcjA7BggrBgEFBQcBAQQvMC0wKwYIKwYBBQUHMAKGH2h0dHA6Ly8xMjcuMC4w
LjE6ODIwMC92MS9wa2kvY2EwIQYDVR0RBBowGIIQY2VydC5teXZhdWx0LmNvbYcE
fwAAATAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vMTI3LjAuMC4xOjgyMDAvdjEv
cGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEAWGholPN8buDYwKbUiDavbzjsxUIX
lU4MxEqOHw7CD3qIYIauPboLvB9EldBQwhgOOy607Yvdg3rtyYwyBFwPhHo/hK3Z
6mn4hc6TF2V+AUdHBvGzp2dbYLeo8noVoWbQ/lBulggwlIHNNF6+a3kALqsqk1Ch
f/hzsjFnDhAlNcYFgG8TgfE2lE/FckvejPqBffo7Q3I+wVAw0buqiz5QL81NOT+D
Y2S9LLKLRaCsWo9wRU1Az4Rhd7vK5SEMh16jJ82GyEODWPvuxOTI1MnzfnbWyLYe
TTp6YBjGMVf1I6NEcWNur7U17uIOiQjMZ9krNvoMJ1A/cxCoZ98QHgcIPg==
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA2N4DwdvZJHW7tJjzuidHn2oEIUolwfTsg7i3+bDJOglQ1lVm
peYSEpMgZsP5V4Be6gtlxSor6STq3BkHeRKfaLQasiS8yG9CFnV7jyK1Z1w8HHVq
/Osm19oguNsyln214I8xoo3gFaZdFCSygY/n5Oaujb5ES+RVC+k0Veyq5/Zq5p61
fSeSKldeaU3fVtmlSeiKlryDwgArWSrpalp+O9O7stl9ltUrav+ap6daQ7IYhpNV
T8sQsEOssN4hHIHnG2KN70H4VnL+0PC6UiDPWWKV7ujphfnM6kWUvYBEZfsIqZpq
zdTHWFa07kHpttQvzzWeI6OF1JNUhceia3APpQIDAQABAoIBAQCH3vEzr+3nreug
RoPNCXcSJXXY9X+aeT0FeeGqClzIg7Wl03OwVOjVwl/2gqnhbIgK0oE8eiNwurR6
mSPZcxV0oAJpwiKU4T/imlCDaReGXn86xUX2l82KRxthNdQH/VLKEmzij0jpx4Vh
bWx5SBPdkbmjDKX1dmTiRYWIn/KjyNPvNvmtwdi8Qluhf4eJcNEUr2BtblnGOmfL
FdSu+brPJozpoQ1QdDnbAQRgqnh7Shl0tT85whQi0uquqIj1gEOGVjmBvDDnL3GV
WOENTKqsmIIoEzdZrql1pfmYTk7WNaD92bfpN128j8BF7RmAV4/DphH0pvK05y9m
tmRhyHGxAoGBAOV2BBocsm6xup575VqmFN+EnIOiTn+haOvfdnVsyQHnth63fOQx
PNtMpTPR1OMKGpJ13e2bV0IgcYRsRkScVkUtoa/17VIgqZXffnJJ0A/HT67uKBq3
8o7RrtyK5N20otw0lZHyqOPhyCdpSsurDhNON1kPVJVYY4N1RiIxfut/AoGBAPHz
HfsJ5ZkyELE9N/r4fce04lprxWH+mQGK0/PfjS9caXPhj/r5ZkVMvzWesF3mmnY8
goE5S35TuTvV1+6rKGizwlCFAQlyXJiFpOryNWpLwCmDDSzLcm+sToAlML3tMgWU
jM3dWHx3C93c3ft4rSWJaUYI9JbHsMzDW6Yh+GbbAoGBANIbKwxh5Hx5XwEJP2yu
kIROYCYkMy6otHLujgBdmPyWl+suZjxoXWoMl2SIqR8vPD+Jj6mmyNJy9J6lqf3f
DRuQ+fEuBZ1i7QWfvJ+XuN0JyovJ5Iz6jC58D1pAD+p2IX3y5FXcVQs8zVJRFjzB
p0TEJOf2oqORaKWRd6ONoMKvAoGALKu6aVMWdQZtVov6/fdLIcgf0pn7Q3CCR2qe
X3Ry2L+zKJYIw0mwvDLDSt8VqQCenB3n6nvtmFFU7ds5lvM67rnhsoQcAOaAehiS
rl4xxoJd5Ewx7odRhZTGmZpEOYzFo4odxRSM9c30/u18fqV1Mm0AZtHYds4/sk6P
aUj0V+kCgYBMpGrJk8RSez5g0XZ35HfpI4ENoWbiwB59FIpWsLl2LADEh29eC455
t9Muq7MprBVBHQo11TMLLFxDIjkuMho/gcKgpYXCt0LfiNm8EZehvLJUXH+3WqUx
we6ywrbFCs6LaxaOCtTiLsN+GbZCatITL0UJaeBmTAbiw0KQjUuZPQ==
-----END RSA PRIVATE KEY-----

View File

@ -1,304 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
// Package ocsp implements an OCSP responder based on a generic storage backend.
// It provides a couple of sample implementations.
// Because OCSP responders handle high query volumes, we have to be careful
// about how much logging we do. Error-level logs are reserved for problems
// internal to the server, that can be fixed by an administrator. Any type of
// incorrect input from a user should be logged and Info or below. For things
// that are logged on every request, Debug is the appropriate level.
//
// From https://github.com/cloudflare/cfssl/blob/master/ocsp/responder.go
package cert
import (
"crypto"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"golang.org/x/crypto/ocsp"
)
var (
malformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
internalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
tryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03}
sigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
unauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
// ErrNotFound indicates the request OCSP response was not found. It is used to
// indicate that the responder should reply with unauthorizedErrorResponse.
ErrNotFound = errors.New("Request OCSP Response not found")
)
// Source represents the logical source of OCSP responses, i.e.,
// the logic that actually chooses a response based on a request. In
// order to create an actual responder, wrap one of these in a Responder
// object and pass it to http.Handle. By default the Responder will set
// the headers Cache-Control to "max-age=(response.NextUpdate-now), public, no-transform, must-revalidate",
// Last-Modified to response.ThisUpdate, Expires to response.NextUpdate,
// ETag to the SHA256 hash of the response, and Content-Type to
// application/ocsp-response. If you want to override these headers,
// or set extra headers, your source should return a http.Header
// with the headers you wish to set. If you don'log want to set any
// extra headers you may return nil instead.
type Source interface {
Response(*ocsp.Request) ([]byte, http.Header, error)
}
// An InMemorySource is a map from serialNumber -> der(response)
type InMemorySource map[string][]byte
// Response looks up an OCSP response to provide for a given request.
// InMemorySource looks up a response purely based on serial number,
// without regard to what issuer the request is asking for.
func (src InMemorySource) Response(request *ocsp.Request) ([]byte, http.Header, error) {
response, present := src[request.SerialNumber.String()]
if !present {
return nil, nil, ErrNotFound
}
return response, nil, nil
}
// Stats is a basic interface that allows users to record information
// about returned responses
type Stats interface {
ResponseStatus(ocsp.ResponseStatus)
}
type logger interface {
Log(args ...any)
}
// A Responder object provides the HTTP logic to expose a
// Source of OCSP responses.
type Responder struct {
log logger
Source Source
stats Stats
}
// NewResponder instantiates a Responder with the give Source.
func NewResponder(t logger, source Source, stats Stats) *Responder {
return &Responder{
Source: source,
stats: stats,
log: t,
}
}
func overrideHeaders(response http.ResponseWriter, headers http.Header) {
for k, v := range headers {
if len(v) == 1 {
response.Header().Set(k, v[0])
} else if len(v) > 1 {
response.Header().Del(k)
for _, e := range v {
response.Header().Add(k, e)
}
}
}
}
// hashToString contains mappings for the only hash functions
// x/crypto/ocsp supports
var hashToString = map[crypto.Hash]string{
crypto.SHA1: "SHA1",
crypto.SHA256: "SHA256",
crypto.SHA384: "SHA384",
crypto.SHA512: "SHA512",
}
// A Responder can process both GET and POST requests. The mapping
// from an OCSP request to an OCSP response is done by the Source;
// the Responder simply decodes the request, and passes back whatever
// response is provided by the source.
// Note: The caller must use http.StripPrefix to strip any path components
// (including '/') on GET requests.
// Do not use this responder in conjunction with http.NewServeMux, because the
// default handler will try to canonicalize path components by changing any
// strings of repeated '/' into a single '/', which will break the base64
// encoding.
func (rs *Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// By default we set a 'max-age=0, no-cache' Cache-Control header, this
// is only returned to the client if a valid authorized OCSP response
// is not found or an error is returned. If a response if found the header
// will be altered to contain the proper max-age and modifiers.
response.Header().Add("Cache-Control", "max-age=0, no-cache")
// Read response from request
var requestBody []byte
var err error
switch request.Method {
case "GET":
base64Request, err := url.QueryUnescape(request.URL.Path)
if err != nil {
rs.log.Log("Error decoding URL:", request.URL.Path)
response.WriteHeader(http.StatusBadRequest)
return
}
// url.QueryUnescape not only unescapes %2B escaping, but it additionally
// turns the resulting '+' into a space, which makes base64 decoding fail.
// So we go back afterwards and turn ' ' back into '+'. This means we
// accept some malformed input that includes ' ' or %20, but that's fine.
base64RequestBytes := []byte(base64Request)
for i := range base64RequestBytes {
if base64RequestBytes[i] == ' ' {
base64RequestBytes[i] = '+'
}
}
// In certain situations a UA may construct a request that has a double
// slash between the host name and the base64 request body due to naively
// constructing the request URL. In that case strip the leading slash
// so that we can still decode the request.
if len(base64RequestBytes) > 0 && base64RequestBytes[0] == '/' {
base64RequestBytes = base64RequestBytes[1:]
}
requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes))
if err != nil {
rs.log.Log("Error decoding base64 from URL", string(base64RequestBytes))
response.WriteHeader(http.StatusBadRequest)
return
}
case "POST":
requestBody, err = ioutil.ReadAll(request.Body)
if err != nil {
rs.log.Log("Problem reading body of POST", err)
response.WriteHeader(http.StatusBadRequest)
return
}
default:
response.WriteHeader(http.StatusMethodNotAllowed)
return
}
b64Body := base64.StdEncoding.EncodeToString(requestBody)
rs.log.Log("Received OCSP request", b64Body)
// All responses after this point will be OCSP.
// We could check for the content type of the request, but that
// seems unnecessariliy restrictive.
response.Header().Add("Content-Type", "application/ocsp-response")
// Parse response as an OCSP request
// XXX: This fails if the request contains the nonce extension.
// We don'log intend to support nonces anyway, but maybe we
// should return unauthorizedRequest instead of malformed.
ocspRequest, err := ocsp.ParseRequest(requestBody)
if err != nil {
rs.log.Log("Error decoding request body", b64Body)
response.WriteHeader(http.StatusBadRequest)
response.Write(malformedRequestErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.Malformed)
}
return
}
// Look up OCSP response from source
ocspResponse, headers, err := rs.Source.Response(ocspRequest)
if err != nil {
if err == ErrNotFound {
rs.log.Log("No response found for request: serial %x, request body %s",
ocspRequest.SerialNumber, b64Body)
response.Write(unauthorizedErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.Unauthorized)
}
return
}
rs.log.Log("Error retrieving response for request: serial %x, request body %s, error",
ocspRequest.SerialNumber, b64Body, err)
response.WriteHeader(http.StatusInternalServerError)
response.Write(internalErrorErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.InternalError)
}
return
}
parsedResponse, err := ocsp.ParseResponse(ocspResponse, nil)
if err != nil {
rs.log.Log("Error parsing response for serial %x",
ocspRequest.SerialNumber, err)
response.Write(internalErrorErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.InternalError)
}
return
}
// Write OCSP response to response
response.Header().Add("Last-Modified", parsedResponse.ThisUpdate.Format(time.RFC1123))
response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123))
now := time.Now()
maxAge := 0
if now.Before(parsedResponse.NextUpdate) {
maxAge = int(parsedResponse.NextUpdate.Sub(now) / time.Second)
} else {
// TODO(#530): we want max-age=0 but this is technically an authorized OCSP response
// (despite being stale) and 5019 forbids attaching no-cache
maxAge = 0
}
response.Header().Set(
"Cache-Control",
fmt.Sprintf(
"max-age=%d, public, no-transform, must-revalidate",
maxAge,
),
)
responseHash := sha256.Sum256(ocspResponse)
response.Header().Add("ETag", fmt.Sprintf("\"%X\"", responseHash))
if headers != nil {
overrideHeaders(response, headers)
}
// RFC 7232 says that a 304 response must contain the above
// headers if they would also be sent for a 200 for the same
// request, so we have to wait until here to do this
if etag := request.Header.Get("If-None-Match"); etag != "" {
if etag == fmt.Sprintf("\"%X\"", responseHash) {
response.WriteHeader(http.StatusNotModified)
return
}
}
response.WriteHeader(http.StatusOK)
response.Write(ocspResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.Success)
}
}
/*
Copyright (c) 2014 CloudFlare Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

File diff suppressed because it is too large Load Diff

View File

@ -1,361 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package radius
import (
"context"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
"github.com/hashicorp/vault/sdk/helper/docker"
"github.com/hashicorp/vault/sdk/logical"
)
const (
testSysTTL = time.Hour * 10
testSysMaxTTL = time.Hour * 20
envRadiusRadiusHost = "RADIUS_HOST"
envRadiusPort = "RADIUS_PORT"
envRadiusSecret = "RADIUS_SECRET"
envRadiusUsername = "RADIUS_USERNAME"
envRadiusUserPass = "RADIUS_USERPASS"
)
func prepareRadiusTestContainer(t *testing.T) (func(), string, int) {
if os.Getenv(envRadiusRadiusHost) != "" {
port, _ := strconv.Atoi(os.Getenv(envRadiusPort))
return func() {}, os.Getenv(envRadiusRadiusHost), port
}
// Now allow any client to connect to this radiusd instance by writing our
// own clients.conf file.
//
// This is necessary because we lack control over the container's network
// IPs. We might be running in Circle CI (with variable IPs per new
// network) or in Podman (which uses an entirely different set of default
// ranges than Docker).
//
// See also: https://freeradius.org/radiusd/man/clients.conf.html
ctx := context.Background()
clientsConfig := `
client 0.0.0.0/1 {
ipaddr = 0.0.0.0/1
secret = testing123
shortname = all-clients-first
}
client 128.0.0.0/1 {
ipaddr = 128.0.0.0/1
secret = testing123
shortname = all-clients-second
}
`
containerfile := `
FROM docker.mirror.hashicorp.services/jumanjiman/radiusd:latest
COPY clients.conf /etc/raddb/clients.conf
`
bCtx := docker.NewBuildContext()
bCtx["clients.conf"] = docker.PathContentsFromBytes([]byte(clientsConfig))
imageName := "vault_radiusd_any_client"
imageTag := "latest"
runner, err := docker.NewServiceRunner(docker.RunOptions{
ImageRepo: imageName,
ImageTag: imageTag,
ContainerName: "radiusd",
Cmd: []string{"-f", "-l", "stdout", "-X"},
Ports: []string{"1812/udp"},
LogConsumer: func(s string) {
if t.Failed() {
t.Logf("container logs: %s", s)
}
},
})
if err != nil {
t.Fatalf("Could not provision docker service runner: %s", err)
}
output, err := runner.BuildImage(ctx, containerfile, bCtx,
docker.BuildRemove(true), docker.BuildForceRemove(true),
docker.BuildPullParent(true),
docker.BuildTags([]string{imageName + ":" + imageTag}))
if err != nil {
t.Fatalf("Could not build new image: %v", err)
}
t.Logf("Image build output: %v", string(output))
svc, err := runner.StartService(context.Background(), func(ctx context.Context, host string, port int) (docker.ServiceConfig, error) {
time.Sleep(2 * time.Second)
return docker.NewServiceHostPort(host, port), nil
})
if err != nil {
t.Fatalf("Could not start docker radiusd: %s", err)
}
pieces := strings.Split(svc.Config.Address(), ":")
port, _ := strconv.Atoi(pieces[1])
return svc.Cleanup, pieces[0], port
}
func TestBackend_Config(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
configDataBasic := map[string]interface{}{
"host": "test.radius.hostname.com",
"secret": "test-secret",
}
configDataMissingRequired := map[string]interface{}{
"host": "test.radius.hostname.com",
}
configDataEmptyPort := map[string]interface{}{
"host": "test.radius.hostname.com",
"port": "",
"secret": "test-secret",
}
configDataInvalidPort := map[string]interface{}{
"host": "test.radius.hostname.com",
"port": "notnumeric",
"secret": "test-secret",
}
configDataInvalidBool := map[string]interface{}{
"host": "test.radius.hostname.com",
"secret": "test-secret",
"unregistered_user_policies": "test",
}
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: false,
// PreCheck: func() { testAccPreCheck(t) },
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testConfigWrite(t, configDataBasic, false),
testConfigWrite(t, configDataMissingRequired, true),
testConfigWrite(t, configDataEmptyPort, true),
testConfigWrite(t, configDataInvalidPort, true),
testConfigWrite(t, configDataInvalidBool, true),
},
})
}
func TestBackend_users(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testStepUpdateUser(t, "web", "foo"),
testStepUpdateUser(t, "web2", "foo"),
testStepUpdateUser(t, "web3", "foo"),
testStepUserList(t, []string{"web", "web2", "web3"}),
},
})
}
func TestBackend_acceptance(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
cleanup, host, port := prepareRadiusTestContainer(t)
defer cleanup()
// These defaults are specific to the jumanjiman/radiusd docker image
username := os.Getenv(envRadiusUsername)
if username == "" {
username = "test"
}
password := os.Getenv(envRadiusUserPass)
if password == "" {
password = "test"
}
secret := os.Getenv(envRadiusSecret)
if len(secret) == 0 {
secret = "testing123"
}
configDataAcceptanceAllowUnreg := map[string]interface{}{
"host": host,
"port": strconv.Itoa(port),
"secret": secret,
"unregistered_user_policies": "policy1,policy2",
}
if configDataAcceptanceAllowUnreg["port"] == "" {
configDataAcceptanceAllowUnreg["port"] = "1812"
}
configDataAcceptanceNoAllowUnreg := map[string]interface{}{
"host": host,
"port": strconv.Itoa(port),
"secret": secret,
"unregistered_user_policies": "",
}
if configDataAcceptanceNoAllowUnreg["port"] == "" {
configDataAcceptanceNoAllowUnreg["port"] = "1812"
}
dataRealpassword := map[string]interface{}{
"password": password,
}
dataWrongpassword := map[string]interface{}{
"password": "wrongpassword",
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
PreCheck: testAccPreCheck(t, host, port),
Steps: []logicaltest.TestStep{
// Login with valid but unknown user will fail because unregistered_user_policies is empty
testConfigWrite(t, configDataAcceptanceNoAllowUnreg, false),
testAccUserLogin(t, username, dataRealpassword, true),
// Once the user is registered auth will succeed
testStepUpdateUser(t, username, ""),
testAccUserLoginPolicy(t, username, dataRealpassword, []string{"default"}, false),
testStepUpdateUser(t, username, "foopolicy"),
testAccUserLoginPolicy(t, username, dataRealpassword, []string{"default", "foopolicy"}, false),
testAccStepDeleteUser(t, username),
// When unregistered_user_policies is specified, an unknown user will be granted access and granted the listed policies
testConfigWrite(t, configDataAcceptanceAllowUnreg, false),
testAccUserLoginPolicy(t, username, dataRealpassword, []string{"default", "policy1", "policy2"}, false),
// More tests
testAccUserLogin(t, "nonexistinguser", dataRealpassword, true),
testAccUserLogin(t, username, dataWrongpassword, true),
testStepUpdateUser(t, username, "foopolicy"),
testAccUserLoginPolicy(t, username, dataRealpassword, []string{"default", "foopolicy"}, false),
testStepUpdateUser(t, username, "foopolicy, secondpolicy"),
testAccUserLoginPolicy(t, username, dataRealpassword, []string{"default", "foopolicy", "secondpolicy"}, false),
testAccUserLoginPolicy(t, username, dataRealpassword, []string{"default", "foopolicy", "secondpolicy", "thirdpolicy"}, true),
},
})
}
func testAccPreCheck(t *testing.T, host string, port int) func() {
return func() {
if host == "" {
t.Fatal("Host must be set for acceptance tests")
}
if port == 0 {
t.Fatal("Port must be non-zero for acceptance tests")
}
}
}
func testConfigWrite(t *testing.T, d map[string]interface{}, expectError bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "config",
Data: d,
ErrorOk: expectError,
}
}
func testAccStepDeleteUser(t *testing.T, n string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.DeleteOperation,
Path: "users/" + n,
}
}
func testStepUserList(t *testing.T, users []string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ListOperation,
Path: "users",
Check: func(resp *logical.Response) error {
if resp.IsError() {
return fmt.Errorf("got error response: %#v", *resp)
}
if !reflect.DeepEqual(users, resp.Data["keys"].([]string)) {
return fmt.Errorf("expected:\n%#v\ngot:\n%#v\n", users, resp.Data["keys"])
}
return nil
},
}
}
func testStepUpdateUser(
t *testing.T, name string, policies string,
) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "users/" + name,
Data: map[string]interface{}{
"policies": policies,
},
}
}
func testAccUserLogin(t *testing.T, user string, data map[string]interface{}, expectError bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login/" + user,
Data: data,
ErrorOk: expectError,
Unauthenticated: true,
}
}
func testAccUserLoginPolicy(t *testing.T, user string, data map[string]interface{}, policies []string, expectError bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login/" + user,
Data: data,
ErrorOk: expectError,
Unauthenticated: true,
// Check: logicaltest.TestCheckAuth(policies),
Check: func(resp *logical.Response) error {
res := logicaltest.TestCheckAuth(policies)(resp)
if res != nil && expectError {
return nil
}
return res
},
}
}

View File

@ -1,419 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package userpass
import (
"context"
"crypto/tls"
"fmt"
"reflect"
"testing"
"time"
"github.com/go-test/deep"
sockaddr "github.com/hashicorp/go-sockaddr"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
"github.com/hashicorp/vault/sdk/helper/policyutil"
"github.com/hashicorp/vault/sdk/helper/tokenutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/mapstructure"
)
const (
testSysTTL = time.Hour * 10
testSysMaxTTL = time.Hour * 20
)
func TestBackend_CRUD(t *testing.T) {
var resp *logical.Response
var err error
storage := &logical.InmemStorage{}
config := logical.TestBackendConfig()
config.StorageView = storage
ctx := context.Background()
b, err := Factory(ctx, config)
if err != nil {
t.Fatal(err)
}
if b == nil {
t.Fatalf("failed to create backend")
}
localhostSockAddr, err := sockaddr.NewSockAddr("127.0.0.1")
if err != nil {
t.Fatal(err)
}
// Use new token_ forms
resp, err = b.HandleRequest(ctx, &logical.Request{
Path: "users/testuser",
Operation: logical.CreateOperation,
Storage: storage,
Data: map[string]interface{}{
"password": "testpassword",
"token_ttl": 5,
"token_max_ttl": 10,
"token_policies": []string{"foo"},
"token_bound_cidrs": []string{"127.0.0.1"},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
resp, err = b.HandleRequest(ctx, &logical.Request{
Path: "users/testuser",
Operation: logical.ReadOperation,
Storage: storage,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
if resp.Data["token_ttl"].(int64) != 5 && resp.Data["token_max_ttl"].(int64) != 10 {
t.Fatalf("bad: token_ttl and token_max_ttl are not set correctly")
}
if diff := deep.Equal(resp.Data["token_policies"], []string{"foo"}); diff != nil {
t.Fatal(diff)
}
if diff := deep.Equal(resp.Data["token_bound_cidrs"], []*sockaddr.SockAddrMarshaler{{localhostSockAddr}}); diff != nil {
t.Fatal(diff)
}
localhostSockAddr, err = sockaddr.NewSockAddr("127.0.1.1")
if err != nil {
t.Fatal(err)
}
// Use the old forms and verify that they zero out the new ones and then
// the new ones read with the expected value
resp, err = b.HandleRequest(ctx, &logical.Request{
Path: "users/testuser",
Operation: logical.UpdateOperation,
Storage: storage,
Data: map[string]interface{}{
"ttl": "5m",
"max_ttl": "10m",
"policies": []string{"bar"},
"bound_cidrs": []string{"127.0.1.1"},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
resp, err = b.HandleRequest(ctx, &logical.Request{
Path: "users/testuser",
Operation: logical.ReadOperation,
Storage: storage,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
if resp.Data["ttl"].(int64) != 300 && resp.Data["max_ttl"].(int64) != 600 {
t.Fatalf("bad: ttl and max_ttl are not set correctly")
}
if resp.Data["token_ttl"].(int64) != 300 && resp.Data["token_max_ttl"].(int64) != 600 {
t.Fatalf("bad: token_ttl and token_max_ttl are not set correctly")
}
if diff := deep.Equal(resp.Data["policies"], []string{"bar"}); diff != nil {
t.Fatal(diff)
}
if diff := deep.Equal(resp.Data["token_policies"], []string{"bar"}); diff != nil {
t.Fatal(diff)
}
if diff := deep.Equal(resp.Data["bound_cidrs"], []*sockaddr.SockAddrMarshaler{{localhostSockAddr}}); diff != nil {
t.Fatal(diff)
}
if diff := deep.Equal(resp.Data["token_bound_cidrs"], []*sockaddr.SockAddrMarshaler{{localhostSockAddr}}); diff != nil {
t.Fatal(diff)
}
}
func TestBackend_basic(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "web", "password", "foo"),
testAccStepUser(t, "web2", "password", "foo"),
testAccStepUser(t, "web3", "password", "foo"),
testAccStepList(t, []string{"web", "web2", "web3"}),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
},
})
}
func TestBackend_userCrud(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "web", "password", "foo"),
testAccStepReadUser(t, "web", "foo"),
testAccStepDeleteUser(t, "web"),
testAccStepReadUser(t, "web", ""),
},
})
}
func TestBackend_userCreateOperation(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testUserCreateOperation(t, "web", "password", "foo"),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
},
})
}
func TestBackend_passwordUpdate(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "web", "password", "foo"),
testAccStepReadUser(t, "web", "foo"),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
testUpdatePassword(t, "web", "newpassword"),
testAccStepLogin(t, "web", "newpassword", []string{"default", "foo"}),
},
})
}
func TestBackend_policiesUpdate(t *testing.T) {
b, err := Factory(context.Background(), &logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "web", "password", "foo"),
testAccStepReadUser(t, "web", "foo"),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
testUpdatePolicies(t, "web", "foo,bar"),
testAccStepReadUser(t, "web", "bar,foo"),
testAccStepLogin(t, "web", "password", []string{"bar", "default", "foo"}),
},
})
}
func testUpdatePassword(t *testing.T, user, password string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "users/" + user + "/password",
Data: map[string]interface{}{
"password": password,
},
}
}
func testUpdatePolicies(t *testing.T, user, policies string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "users/" + user + "/policies",
Data: map[string]interface{}{
"policies": policies,
},
}
}
func testAccStepList(t *testing.T, users []string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ListOperation,
Path: "users",
Check: func(resp *logical.Response) error {
if resp.IsError() {
return fmt.Errorf("got error response: %#v", *resp)
}
exp := []string{"web", "web2", "web3"}
if !reflect.DeepEqual(exp, resp.Data["keys"].([]string)) {
return fmt.Errorf("expected:\n%#v\ngot:\n%#v\n", exp, resp.Data["keys"])
}
return nil
},
}
}
func testAccStepLogin(t *testing.T, user string, pass string, policies []string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login/" + user,
Data: map[string]interface{}{
"password": pass,
},
Unauthenticated: true,
Check: logicaltest.TestCheckAuth(policies),
ConnState: &tls.ConnectionState{},
}
}
func testUserCreateOperation(
t *testing.T, name string, password string, policies string,
) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.CreateOperation,
Path: "users/" + name,
Data: map[string]interface{}{
"password": password,
"policies": policies,
},
}
}
func testAccStepUser(
t *testing.T, name string, password string, policies string,
) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "users/" + name,
Data: map[string]interface{}{
"password": password,
"policies": policies,
},
}
}
func testAccStepDeleteUser(t *testing.T, n string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.DeleteOperation,
Path: "users/" + n,
}
}
func testAccStepReadUser(t *testing.T, name string, policies string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "users/" + name,
Check: func(resp *logical.Response) error {
if resp == nil {
if policies == "" {
return nil
}
return fmt.Errorf("bad: %#v", resp)
}
var d struct {
Policies []string `mapstructure:"policies"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
if !reflect.DeepEqual(d.Policies, policyutil.ParsePolicies(policies)) {
return fmt.Errorf("bad: %#v", resp)
}
return nil
},
}
}
func TestBackend_UserUpgrade(t *testing.T) {
s := &logical.InmemStorage{}
config := logical.TestBackendConfig()
config.StorageView = s
ctx := context.Background()
b := Backend()
if b == nil {
t.Fatalf("failed to create backend")
}
if err := b.Setup(ctx, config); err != nil {
t.Fatal(err)
}
foo := &UserEntry{
Policies: []string{"foo"},
TTL: time.Second,
MaxTTL: time.Second,
BoundCIDRs: []*sockaddr.SockAddrMarshaler{{SockAddr: sockaddr.MustIPAddr("127.0.0.1")}},
}
entry, err := logical.StorageEntryJSON("user/foo", foo)
if err != nil {
t.Fatal(err)
}
err = s.Put(ctx, entry)
if err != nil {
t.Fatal(err)
}
userEntry, err := b.user(ctx, s, "foo")
if err != nil {
t.Fatal(err)
}
exp := &UserEntry{
Policies: []string{"foo"},
TTL: time.Second,
MaxTTL: time.Second,
BoundCIDRs: []*sockaddr.SockAddrMarshaler{{SockAddr: sockaddr.MustIPAddr("127.0.0.1")}},
TokenParams: tokenutil.TokenParams{
TokenPolicies: []string{"foo"},
TokenTTL: time.Second,
TokenMaxTTL: time.Second,
TokenBoundCIDRs: []*sockaddr.SockAddrMarshaler{{SockAddr: sockaddr.MustIPAddr("127.0.0.1")}},
},
}
if diff := deep.Equal(userEntry, exp); diff != nil {
t.Fatal(diff)
}
}

View File

@ -1,85 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package userpass
import (
"fmt"
"reflect"
"testing"
stepwise "github.com/hashicorp/vault-testing-stepwise"
dockerEnvironment "github.com/hashicorp/vault-testing-stepwise/environments/docker"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/policyutil"
"github.com/mitchellh/mapstructure"
)
func TestAccBackend_stepwise_UserCrud(t *testing.T) {
customPluginName := "my-userpass"
envOptions := &stepwise.MountOptions{
RegistryName: customPluginName,
PluginType: api.PluginTypeCredential,
PluginName: "userpass",
MountPathPrefix: customPluginName,
}
stepwise.Run(t, stepwise.Case{
Environment: dockerEnvironment.NewEnvironment(customPluginName, envOptions),
Steps: []stepwise.Step{
testAccStepwiseUser(t, "web", "password", "foo"),
testAccStepwiseReadUser(t, "web", "foo"),
testAccStepwiseDeleteUser(t, "web"),
testAccStepwiseReadUser(t, "web", ""),
},
})
}
func testAccStepwiseUser(
t *testing.T, name string, password string, policies string,
) stepwise.Step {
return stepwise.Step{
Operation: stepwise.UpdateOperation,
Path: "users/" + name,
Data: map[string]interface{}{
"password": password,
"policies": policies,
},
}
}
func testAccStepwiseDeleteUser(t *testing.T, name string) stepwise.Step {
return stepwise.Step{
Operation: stepwise.DeleteOperation,
Path: "users/" + name,
}
}
func testAccStepwiseReadUser(t *testing.T, name string, policies string) stepwise.Step {
return stepwise.Step{
Operation: stepwise.ReadOperation,
Path: "users/" + name,
Assert: func(resp *api.Secret, err error) error {
if resp == nil {
if policies == "" {
return nil
}
return fmt.Errorf("unexpected nil response")
}
var d struct {
Policies []string `mapstructure:"policies"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
expectedPolicies := policyutil.ParsePolicies(policies)
if !reflect.DeepEqual(d.Policies, expectedPolicies) {
return fmt.Errorf("Actual policies: %#v\nExpected policies: %#v", d.Policies, expectedPolicies)
}
return nil
},
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,792 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"testing"
"github.com/hashicorp/vault/sdk/helper/base62"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// Test_newClientCertificateGenerator tests the ClientCertificateGenerator struct based on the config
func Test_newClientCertificateGenerator(t *testing.T) {
type args struct {
config map[string]interface{}
}
tests := []struct {
name string
args args
want ClientCertificateGenerator
wantErr bool
}{
{
name: "newClientCertificateGenerator with nil config",
args: args{
config: nil,
},
want: ClientCertificateGenerator{
CommonNameTemplate: "",
CAPrivateKey: "",
CACert: "",
KeyType: "",
KeyBits: 0,
SignatureBits: 0,
},
},
{
name: "newClientCertificateGenerator with zero value key_type",
args: args{
config: map[string]interface{}{
"key_type": "",
},
},
want: ClientCertificateGenerator{
KeyType: "",
},
},
{
name: "newClientCertificateGenerator with rsa value key_type",
args: args{
config: map[string]interface{}{
"key_type": "rsa",
},
},
want: ClientCertificateGenerator{
KeyType: "rsa",
KeyBits: 2048,
SignatureBits: 256,
},
},
{
name: "newClientCertificateGenerator with ec value key_type",
args: args{
config: map[string]interface{}{
"key_type": "ec",
},
},
want: ClientCertificateGenerator{
KeyType: "ec",
KeyBits: 256,
SignatureBits: 256,
},
},
{
name: "newClientCertificateGenerator with ed25519 value key_type",
args: args{
config: map[string]interface{}{
"key_type": "ed25519",
},
},
want: ClientCertificateGenerator{
KeyType: "ed25519",
SignatureBits: 256,
},
},
{
name: "newClientCertificateGenerator with invalid key_type",
args: args{
config: map[string]interface{}{
"key_type": "ece",
},
},
wantErr: true,
},
{
name: "newClientCertificateGenerator with zero value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "0",
},
},
want: ClientCertificateGenerator{
KeyBits: 0,
},
},
{
name: "newClientCertificateGenerator with 2048 value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "2048",
},
},
want: ClientCertificateGenerator{
KeyBits: 2048,
},
},
{
name: "newClientCertificateGenerator with 3072 value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "3072",
},
},
want: ClientCertificateGenerator{
KeyBits: 3072,
},
},
{
name: "newClientCertificateGenerator with 4096 value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "4096",
},
},
want: ClientCertificateGenerator{
KeyBits: 4096,
},
},
{
name: "newClientCertificateGenerator with 224 value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "224",
},
},
want: ClientCertificateGenerator{
KeyBits: 224,
},
},
{
name: "newClientCertificateGenerator with 256 value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "256",
},
},
want: ClientCertificateGenerator{
KeyBits: 256,
},
},
{
name: "newClientCertificateGenerator with 384 value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "384",
},
},
want: ClientCertificateGenerator{
KeyBits: 384,
},
},
{
name: "newClientCertificateGenerator with 521 value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "521",
},
},
want: ClientCertificateGenerator{
KeyBits: 521,
},
},
{
name: "newClientCertificateGenerator with invalid key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "4097",
},
},
wantErr: true,
},
{
name: "newClientCertificateGenerator with zero value signature_bits",
args: args{
config: map[string]interface{}{
"signature_bits": "0",
},
},
want: ClientCertificateGenerator{
SignatureBits: 0,
},
},
{
name: "newClientCertificateGenerator with 256 value signature_bits",
args: args{
config: map[string]interface{}{
"signature_bits": "256",
},
},
want: ClientCertificateGenerator{
SignatureBits: 256,
},
},
{
name: "newClientCertificateGenerator with 384 value signature_bits",
args: args{
config: map[string]interface{}{
"signature_bits": "384",
},
},
want: ClientCertificateGenerator{
SignatureBits: 384,
},
},
{
name: "newClientCertificateGenerator with 512 value signature_bits",
args: args{
config: map[string]interface{}{
"signature_bits": "512",
},
},
want: ClientCertificateGenerator{
SignatureBits: 512,
},
},
{
name: "newClientCertificateGenerator with invalid signature_bits",
args: args{
config: map[string]interface{}{
"signature_bits": "612",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := newClientCertificateGenerator(tt.args.config)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.Equal(t, tt.want, got)
})
}
}
func Test_newPasswordGenerator(t *testing.T) {
type args struct {
config map[string]interface{}
}
tests := []struct {
name string
args args
want passwordGenerator
wantErr bool
}{
{
name: "newPasswordGenerator with nil config",
args: args{
config: nil,
},
want: passwordGenerator{
PasswordPolicy: "",
},
},
{
name: "newPasswordGenerator without password_policy",
args: args{
config: map[string]interface{}{},
},
want: passwordGenerator{
PasswordPolicy: "",
},
},
{
name: "newPasswordGenerator with password_policy",
args: args{
config: map[string]interface{}{
"password_policy": "test-policy",
},
},
want: passwordGenerator{
PasswordPolicy: "test-policy",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := newPasswordGenerator(tt.args.config)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.Equal(t, tt.want, got)
})
}
}
func Test_newRSAKeyGenerator(t *testing.T) {
type args struct {
config map[string]interface{}
}
tests := []struct {
name string
args args
want rsaKeyGenerator
wantErr bool
}{
{
name: "newRSAKeyGenerator with nil config",
args: args{
config: nil,
},
want: rsaKeyGenerator{
Format: "pkcs8",
KeyBits: 2048,
},
},
{
name: "newRSAKeyGenerator with empty config",
args: args{
config: map[string]interface{}{},
},
want: rsaKeyGenerator{
Format: "pkcs8",
KeyBits: 2048,
},
},
{
name: "newRSAKeyGenerator with zero value format",
args: args{
config: map[string]interface{}{
"format": "",
},
},
want: rsaKeyGenerator{
Format: "pkcs8",
KeyBits: 2048,
},
},
{
name: "newRSAKeyGenerator with zero value key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "0",
},
},
want: rsaKeyGenerator{
Format: "pkcs8",
KeyBits: 2048,
},
},
{
name: "newRSAKeyGenerator with format",
args: args{
config: map[string]interface{}{
"format": "pkcs8",
},
},
want: rsaKeyGenerator{
Format: "pkcs8",
KeyBits: 2048,
},
},
{
name: "newRSAKeyGenerator with format case insensitive",
args: args{
config: map[string]interface{}{
"format": "PKCS8",
},
},
want: rsaKeyGenerator{
Format: "PKCS8",
KeyBits: 2048,
},
},
{
name: "newRSAKeyGenerator with 3072 key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "3072",
},
},
want: rsaKeyGenerator{
Format: "pkcs8",
KeyBits: 3072,
},
},
{
name: "newRSAKeyGenerator with 4096 key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "4096",
},
},
want: rsaKeyGenerator{
Format: "pkcs8",
KeyBits: 4096,
},
},
{
name: "newRSAKeyGenerator with invalid key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "4097",
},
},
wantErr: true,
},
{
name: "newRSAKeyGenerator with invalid format",
args: args{
config: map[string]interface{}{
"format": "pkcs1",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := newRSAKeyGenerator(tt.args.config)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.Equal(t, tt.want, got)
})
}
}
func Test_passwordGenerator_generate(t *testing.T) {
config := logical.TestBackendConfig()
b := Backend(config)
b.Setup(context.Background(), config)
type args struct {
config map[string]interface{}
mock func() interface{}
passGen logical.PasswordGenerator
}
tests := []struct {
name string
args args
wantRegexp string
wantErr bool
}{
{
name: "wrapper missing v4 and v5 interface",
args: args{
mock: func() interface{} {
return nil
},
},
wantErr: true,
},
{
name: "v4: generate password using GenerateCredentials",
args: args{
mock: func() interface{} {
v4Mock := new(mockLegacyDatabase)
v4Mock.On("GenerateCredentials", mock.Anything).
Return("v4-generated-password", nil).
Times(1)
return v4Mock
},
},
wantRegexp: "^v4-generated-password$",
},
{
name: "v5: generate password without policy",
args: args{
mock: func() interface{} {
return new(mockNewDatabase)
},
},
wantRegexp: "^[a-zA-Z0-9-]{20}$",
},
{
name: "v5: generate password with non-existing policy",
args: args{
config: map[string]interface{}{
"password_policy": "not-created",
},
mock: func() interface{} {
return new(mockNewDatabase)
},
},
wantErr: true,
},
{
name: "v5: generate password with existing policy",
args: args{
config: map[string]interface{}{
"password_policy": "test-policy",
},
mock: func() interface{} {
return new(mockNewDatabase)
},
passGen: func() (string, error) {
return base62.Random(30)
},
},
wantRegexp: "^[a-zA-Z0-9]{30}$",
},
{
name: "v5: generate password with existing policy static",
args: args{
config: map[string]interface{}{
"password_policy": "test-policy",
},
mock: func() interface{} {
return new(mockNewDatabase)
},
passGen: func() (string, error) {
return "policy-generated-password", nil
},
},
wantRegexp: "^policy-generated-password$",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the version wrapper with a mock database implementation
wrapper := databaseVersionWrapper{}
switch m := tt.args.mock().(type) {
case *mockNewDatabase:
wrapper.v5 = m
case *mockLegacyDatabase:
wrapper.v4 = m
}
// Set the password policy for the test case
config.System.(*logical.StaticSystemView).SetPasswordPolicy(
"test-policy", tt.args.passGen)
// Generate the password
pg, err := newPasswordGenerator(tt.args.config)
got, err := pg.generate(context.Background(), b, wrapper)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.Regexp(t, tt.wantRegexp, got)
// Assert all expected calls took place on the mock
if m, ok := wrapper.v5.(*mockNewDatabase); ok {
m.AssertExpectations(t)
}
if m, ok := wrapper.v4.(*mockLegacyDatabase); ok {
m.AssertExpectations(t)
}
})
}
}
func Test_passwordGenerator_configMap(t *testing.T) {
type args struct {
config map[string]interface{}
}
tests := []struct {
name string
args args
want map[string]interface{}
}{
{
name: "nil config results in empty map",
args: args{
config: nil,
},
want: map[string]interface{}{},
},
{
name: "empty config results in empty map",
args: args{
config: map[string]interface{}{},
},
want: map[string]interface{}{},
},
{
name: "input config is equal to output config",
args: args{
config: map[string]interface{}{
"password_policy": "test-policy",
},
},
want: map[string]interface{}{
"password_policy": "test-policy",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pg, err := newPasswordGenerator(tt.args.config)
assert.NoError(t, err)
cm, err := pg.configMap()
assert.NoError(t, err)
assert.Equal(t, tt.want, cm)
})
}
}
func Test_rsaKeyGenerator_generate(t *testing.T) {
type args struct {
config map[string]interface{}
}
tests := []struct {
name string
args args
}{
{
name: "generate RSA key with nil default config",
args: args{
config: nil,
},
},
{
name: "generate RSA key with empty default config",
args: args{
config: map[string]interface{}{},
},
},
{
name: "generate RSA key with 2048 key_bits and format",
args: args{
config: map[string]interface{}{
"key_bits": "2048",
"format": "pkcs8",
},
},
},
{
name: "generate RSA key with 2048 key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "2048",
},
},
},
{
name: "generate RSA key with 3072 key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "3072",
},
},
},
{
name: "generate RSA key with 4096 key_bits",
args: args{
config: map[string]interface{}{
"key_bits": "4096",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Generate the RSA key pair
kg, err := newRSAKeyGenerator(tt.args.config)
public, private, err := kg.generate(rand.Reader)
assert.NoError(t, err)
assert.NotEmpty(t, public)
assert.NotEmpty(t, private)
// Decode the public and private key PEMs
pubBlock, pubRest := pem.Decode(public)
privBlock, privRest := pem.Decode(private)
assert.NotNil(t, pubBlock)
assert.Empty(t, pubRest)
assert.Equal(t, "PUBLIC KEY", pubBlock.Type)
assert.NotNil(t, privBlock)
assert.Empty(t, privRest)
assert.Equal(t, "PRIVATE KEY", privBlock.Type)
// Assert that we can parse the public key PEM block
pub, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
assert.NoError(t, err)
assert.NotNil(t, pub)
assert.IsType(t, &rsa.PublicKey{}, pub)
// Assert that we can parse the private key PEM block in
// the configured format
switch kg.Format {
case "pkcs8":
priv, err := x509.ParsePKCS8PrivateKey(privBlock.Bytes)
assert.NoError(t, err)
assert.NotNil(t, priv)
assert.IsType(t, &rsa.PrivateKey{}, priv)
default:
t.Fatal("unknown format")
}
})
}
}
func Test_rsaKeyGenerator_configMap(t *testing.T) {
type args struct {
config map[string]interface{}
}
tests := []struct {
name string
args args
want map[string]interface{}
}{
{
name: "nil config results in defaults",
args: args{
config: nil,
},
want: map[string]interface{}{
"format": "pkcs8",
"key_bits": 2048,
},
},
{
name: "empty config results in defaults",
args: args{
config: map[string]interface{}{},
},
want: map[string]interface{}{
"format": "pkcs8",
"key_bits": 2048,
},
},
{
name: "config with format",
args: args{
config: map[string]interface{}{
"format": "pkcs8",
},
},
want: map[string]interface{}{
"format": "pkcs8",
"key_bits": 2048,
},
},
{
name: "config with key_bits",
args: args{
config: map[string]interface{}{
"key_bits": 4096,
},
},
want: map[string]interface{}{
"format": "pkcs8",
"key_bits": 4096,
},
},
{
name: "config with format and key_bits",
args: args{
config: map[string]interface{}{
"format": "pkcs8",
"key_bits": 3072,
},
},
want: map[string]interface{}{
"format": "pkcs8",
"key_bits": 3072,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kg, err := newRSAKeyGenerator(tt.args.config)
assert.NoError(t, err)
cm, err := kg.configMap()
assert.NoError(t, err)
assert.Equal(t, tt.want, cm)
})
}
}

View File

@ -1,278 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package dbplugin_test
import (
"context"
"errors"
"os"
"testing"
"time"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/namespace"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/database/dbplugin"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)
type mockPlugin struct {
users map[string][]string
}
var _ dbplugin.Database = &mockPlugin{}
func (m *mockPlugin) Type() (string, error) { return "mock", nil }
func (m *mockPlugin) CreateUser(_ context.Context, statements dbplugin.Statements, usernameConf dbplugin.UsernameConfig, expiration time.Time) (username string, password string, err error) {
err = errors.New("err")
if usernameConf.DisplayName == "" || expiration.IsZero() {
return "", "", err
}
if _, ok := m.users[usernameConf.DisplayName]; ok {
return "", "", err
}
m.users[usernameConf.DisplayName] = []string{password}
return usernameConf.DisplayName, "test", nil
}
func (m *mockPlugin) RenewUser(_ context.Context, statements dbplugin.Statements, username string, expiration time.Time) error {
err := errors.New("err")
if username == "" || expiration.IsZero() {
return err
}
if _, ok := m.users[username]; !ok {
return err
}
return nil
}
func (m *mockPlugin) RevokeUser(_ context.Context, statements dbplugin.Statements, username string) error {
err := errors.New("err")
if username == "" {
return err
}
if _, ok := m.users[username]; !ok {
return err
}
delete(m.users, username)
return nil
}
func (m *mockPlugin) RotateRootCredentials(_ context.Context, statements []string) (map[string]interface{}, error) {
return nil, nil
}
func (m *mockPlugin) Init(_ context.Context, conf map[string]interface{}, _ bool) (map[string]interface{}, error) {
err := errors.New("err")
if len(conf) != 1 {
return nil, err
}
return conf, nil
}
func (m *mockPlugin) Initialize(_ context.Context, conf map[string]interface{}, _ bool) error {
err := errors.New("err")
if len(conf) != 1 {
return err
}
return nil
}
func (m *mockPlugin) Close() error {
m.users = nil
return nil
}
func (m *mockPlugin) GenerateCredentials(ctx context.Context) (password string, err error) {
return password, err
}
func (m *mockPlugin) SetCredentials(ctx context.Context, statements dbplugin.Statements, staticConfig dbplugin.StaticUserConfig) (username string, password string, err error) {
return username, password, err
}
func getCluster(t *testing.T) (*vault.TestCluster, logical.SystemView) {
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
cores := cluster.Cores
sys := vault.TestDynamicSystemView(cores[0].Core, nil)
vault.TestAddTestPlugin(t, cores[0].Core, "test-plugin", consts.PluginTypeDatabase, "", "TestPlugin_GRPC_Main", []string{}, "")
return cluster, sys
}
// This is not an actual test case, it's a helper function that will be executed
// by the go-plugin client via an exec call.
func TestPlugin_GRPC_Main(t *testing.T) {
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" && os.Getenv(pluginutil.PluginMetadataModeEnv) != "true" {
return
}
plugin := &mockPlugin{
users: make(map[string][]string),
}
args := []string{"--tls-skip-verify=true"}
apiClientMeta := &api.PluginAPIClientMeta{}
flags := apiClientMeta.FlagSet()
flags.Parse(args)
dbplugin.Serve(plugin, api.VaultPluginTLSProvider(apiClientMeta.GetTLSConfig()))
}
func TestPlugin_Init(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
dbRaw, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}
connectionDetails := map[string]interface{}{
"test": 1,
}
_, err = dbRaw.Init(context.Background(), connectionDetails, true)
if err != nil {
t.Fatalf("err: %s", err)
}
err = dbRaw.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestPlugin_CreateUser(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
db, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}
defer db.Close()
connectionDetails := map[string]interface{}{
"test": 1,
}
_, err = db.Init(context.Background(), connectionDetails, true)
if err != nil {
t.Fatalf("err: %s", err)
}
usernameConf := dbplugin.UsernameConfig{
DisplayName: "test",
RoleName: "test",
}
us, pw, err := db.CreateUser(context.Background(), dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
if us != "test" || pw != "test" {
t.Fatal("expected username and password to be 'test'")
}
// try and save the same user again to verify it saved the first time, this
// should return an error
_, _, err = db.CreateUser(context.Background(), dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
if err == nil {
t.Fatal("expected an error, user wasn't created correctly")
}
}
func TestPlugin_RenewUser(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
db, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}
defer db.Close()
connectionDetails := map[string]interface{}{
"test": 1,
}
_, err = db.Init(context.Background(), connectionDetails, true)
if err != nil {
t.Fatalf("err: %s", err)
}
usernameConf := dbplugin.UsernameConfig{
DisplayName: "test",
RoleName: "test",
}
us, _, err := db.CreateUser(context.Background(), dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
err = db.RenewUser(context.Background(), dbplugin.Statements{}, us, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestPlugin_RevokeUser(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
db, err := dbplugin.PluginFactoryVersion(namespace.RootContext(nil), "test-plugin", "", sys, log.NewNullLogger())
if err != nil {
t.Fatalf("err: %s", err)
}
defer db.Close()
connectionDetails := map[string]interface{}{
"test": 1,
}
_, err = db.Init(context.Background(), connectionDetails, true)
if err != nil {
t.Fatalf("err: %s", err)
}
usernameConf := dbplugin.UsernameConfig{
DisplayName: "test",
RoleName: "test",
}
us, _, err := db.CreateUser(context.Background(), dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
// Test default revoke statements
err = db.RevokeUser(context.Background(), dbplugin.Statements{}, us)
if err != nil {
t.Fatalf("err: %s", err)
}
// Try adding the same username back so we can verify it was removed
_, _, err = db.CreateUser(context.Background(), dbplugin.Statements{}, usernameConf, time.Now().Add(time.Minute))
if err != nil {
t.Fatalf("err: %s", err)
}
}

View File

@ -1,104 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"time"
v4 "github.com/hashicorp/vault/sdk/database/dbplugin"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/stretchr/testify/mock"
)
var _ v5.Database = &mockNewDatabase{}
type mockNewDatabase struct {
mock.Mock
}
func (m *mockNewDatabase) Initialize(ctx context.Context, req v5.InitializeRequest) (v5.InitializeResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(v5.InitializeResponse), args.Error(1)
}
func (m *mockNewDatabase) NewUser(ctx context.Context, req v5.NewUserRequest) (v5.NewUserResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(v5.NewUserResponse), args.Error(1)
}
func (m *mockNewDatabase) UpdateUser(ctx context.Context, req v5.UpdateUserRequest) (v5.UpdateUserResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(v5.UpdateUserResponse), args.Error(1)
}
func (m *mockNewDatabase) DeleteUser(ctx context.Context, req v5.DeleteUserRequest) (v5.DeleteUserResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(v5.DeleteUserResponse), args.Error(1)
}
func (m *mockNewDatabase) Type() (string, error) {
args := m.Called()
return args.String(0), args.Error(1)
}
func (m *mockNewDatabase) Close() error {
args := m.Called()
return args.Error(0)
}
var _ v4.Database = &mockLegacyDatabase{}
type mockLegacyDatabase struct {
mock.Mock
}
func (m *mockLegacyDatabase) CreateUser(ctx context.Context, statements v4.Statements, usernameConfig v4.UsernameConfig, expiration time.Time) (username string, password string, err error) {
args := m.Called(ctx, statements, usernameConfig, expiration)
return args.String(0), args.String(1), args.Error(2)
}
func (m *mockLegacyDatabase) RenewUser(ctx context.Context, statements v4.Statements, username string, expiration time.Time) error {
args := m.Called(ctx, statements, username, expiration)
return args.Error(0)
}
func (m *mockLegacyDatabase) RevokeUser(ctx context.Context, statements v4.Statements, username string) error {
args := m.Called(ctx, statements, username)
return args.Error(0)
}
func (m *mockLegacyDatabase) RotateRootCredentials(ctx context.Context, statements []string) (config map[string]interface{}, err error) {
args := m.Called(ctx, statements)
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *mockLegacyDatabase) GenerateCredentials(ctx context.Context) (string, error) {
args := m.Called(ctx)
return args.String(0), args.Error(1)
}
func (m *mockLegacyDatabase) SetCredentials(ctx context.Context, statements v4.Statements, staticConfig v4.StaticUserConfig) (username string, password string, err error) {
args := m.Called(ctx, statements, staticConfig)
return args.String(0), args.String(1), args.Error(2)
}
func (m *mockLegacyDatabase) Init(ctx context.Context, config map[string]interface{}, verifyConnection bool) (saveConfig map[string]interface{}, err error) {
args := m.Called(ctx, config, verifyConnection)
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *mockLegacyDatabase) Type() (string, error) {
args := m.Called()
return args.String(0), args.Error(1)
}
func (m *mockLegacyDatabase) Close() error {
args := m.Called()
return args.Error(0)
}
func (m *mockLegacyDatabase) Initialize(ctx context.Context, config map[string]interface{}, verifyConnection bool) (err error) {
panic("Initialize should not be called")
}

View File

@ -1,169 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"strings"
"testing"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
)
func TestWriteConfig_PluginVersionInStorage(t *testing.T) {
cluster, sys := getCluster(t)
t.Cleanup(cluster.Cleanup)
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
defer b.Cleanup(context.Background())
const hdb = "hana-database-plugin"
hdbBuiltin := versions.GetBuiltinVersion(consts.PluginTypeDatabase, hdb)
// Configure a connection
writePluginVersion := func() {
t.Helper()
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: map[string]interface{}{
"connection_url": "test",
"plugin_name": hdb,
"plugin_version": hdbBuiltin,
"verify_connection": false,
},
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
}
writePluginVersion()
getPluginVersionFromAPI := func() string {
t.Helper()
req := &logical.Request{
Operation: logical.ReadOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
return resp.Data["plugin_version"].(string)
}
pluginVersion := getPluginVersionFromAPI()
if pluginVersion != "" {
t.Fatalf("expected plugin_version empty but got %s", pluginVersion)
}
// Directly store config to get the builtin plugin version into storage,
// simulating a write that happened before upgrading to 1.12.2+
err = storeConfig(context.Background(), config.StorageView, "plugin-test", &DatabaseConfig{
PluginName: hdb,
PluginVersion: hdbBuiltin,
})
if err != nil {
t.Fatal(err)
}
// Now replay the read request, and we still shouldn't get the builtin version back.
pluginVersion = getPluginVersionFromAPI()
if pluginVersion != "" {
t.Fatalf("expected plugin_version empty but got %s", pluginVersion)
}
// Check the underlying data, which should still have the version in storage.
getPluginVersionFromStorage := func() string {
t.Helper()
entry, err := config.StorageView.Get(context.Background(), "config/plugin-test")
if err != nil {
t.Fatal(err)
}
if entry == nil {
t.Fatal()
}
var config DatabaseConfig
if err := entry.DecodeJSON(&config); err != nil {
t.Fatal(err)
}
return config.PluginVersion
}
storagePluginVersion := getPluginVersionFromStorage()
if storagePluginVersion != hdbBuiltin {
t.Fatalf("Expected %s, got: %s", hdbBuiltin, storagePluginVersion)
}
// Trigger a write to storage, which should clean up plugin version in the storage entry.
writePluginVersion()
storagePluginVersion = getPluginVersionFromStorage()
if storagePluginVersion != "" {
t.Fatalf("Expected empty, got: %s", storagePluginVersion)
}
// Finally, confirm API requests still return empty plugin version too
pluginVersion = getPluginVersionFromAPI()
if pluginVersion != "" {
t.Fatalf("expected plugin_version empty but got %s", pluginVersion)
}
}
func TestWriteConfig_HelpfulErrorMessageWhenBuiltinOverridden(t *testing.T) {
cluster, sys := getCluster(t)
t.Cleanup(cluster.Cleanup)
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
defer b.Cleanup(context.Background())
const pg = "postgresql-database-plugin"
pgBuiltin := versions.GetBuiltinVersion(consts.PluginTypeDatabase, pg)
// Configure a connection
data := map[string]interface{}{
"connection_url": "test",
"plugin_name": pg,
"plugin_version": pgBuiltin,
"verify_connection": false,
}
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: data,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("resp:%#v", resp)
}
if !strings.Contains(resp.Error().Error(), "overridden by an unversioned plugin") {
t.Fatalf("expected overridden error but got: %s", resp.Error())
}
}

View File

@ -1,863 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
"time"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace"
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestBackend_Roles_CredentialTypes(t *testing.T) {
config := logical.TestBackendConfig()
config.System = logical.TestSystemView()
config.StorageView = &logical.InmemStorage{}
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
type args struct {
credentialType v5.CredentialType
credentialConfig map[string]string
}
tests := []struct {
name string
args args
wantErr bool
expectedResp map[string]interface{}
}{
{
name: "role with invalid credential type",
args: args{
credentialType: v5.CredentialType(10),
},
wantErr: true,
},
{
name: "role with invalid credential type and valid credential config",
args: args{
credentialType: v5.CredentialType(7),
credentialConfig: map[string]string{
"password_policy": "test-policy",
},
},
wantErr: true,
},
{
name: "role with password credential type",
args: args{
credentialType: v5.CredentialTypePassword,
},
expectedResp: map[string]interface{}{
"credential_type": v5.CredentialTypePassword.String(),
"credential_config": nil,
},
},
{
name: "role with password credential type and configuration",
args: args{
credentialType: v5.CredentialTypePassword,
credentialConfig: map[string]string{
"password_policy": "test-policy",
},
},
expectedResp: map[string]interface{}{
"credential_type": v5.CredentialTypePassword.String(),
"credential_config": map[string]interface{}{
"password_policy": "test-policy",
},
},
},
{
name: "role with rsa_private_key credential type and default configuration",
args: args{
credentialType: v5.CredentialTypeRSAPrivateKey,
},
expectedResp: map[string]interface{}{
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
"credential_config": map[string]interface{}{
"key_bits": json.Number("2048"),
"format": "pkcs8",
},
},
},
{
name: "role with rsa_private_key credential type and 2048 bit configuration",
args: args{
credentialType: v5.CredentialTypeRSAPrivateKey,
credentialConfig: map[string]string{
"key_bits": "2048",
},
},
expectedResp: map[string]interface{}{
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
"credential_config": map[string]interface{}{
"key_bits": json.Number("2048"),
"format": "pkcs8",
},
},
},
{
name: "role with rsa_private_key credential type and 3072 bit configuration",
args: args{
credentialType: v5.CredentialTypeRSAPrivateKey,
credentialConfig: map[string]string{
"key_bits": "3072",
},
},
expectedResp: map[string]interface{}{
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
"credential_config": map[string]interface{}{
"key_bits": json.Number("3072"),
"format": "pkcs8",
},
},
},
{
name: "role with rsa_private_key credential type and 4096 bit configuration",
args: args{
credentialType: v5.CredentialTypeRSAPrivateKey,
credentialConfig: map[string]string{
"key_bits": "4096",
},
},
expectedResp: map[string]interface{}{
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
"credential_config": map[string]interface{}{
"key_bits": json.Number("4096"),
"format": "pkcs8",
},
},
},
{
name: "role with rsa_private_key credential type invalid key_bits configuration",
args: args{
credentialType: v5.CredentialTypeRSAPrivateKey,
credentialConfig: map[string]string{
"key_bits": "256",
},
},
wantErr: true,
},
{
name: "role with rsa_private_key credential type invalid format configuration",
args: args{
credentialType: v5.CredentialTypeRSAPrivateKey,
credentialConfig: map[string]string{
"format": "pkcs1",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := &logical.Request{
Operation: logical.CreateOperation,
Path: "roles/test",
Storage: config.StorageView,
Data: map[string]interface{}{
"db_name": "test-database",
"creation_statements": "CREATE USER {{name}}",
"credential_type": tt.args.credentialType.String(),
"credential_config": tt.args.credentialConfig,
},
}
// Create the role
resp, err := b.HandleRequest(context.Background(), req)
if tt.wantErr {
assert.True(t, resp.IsError(), "expected error")
return
}
assert.False(t, resp.IsError())
assert.Nil(t, err)
// Read the role
req.Operation = logical.ReadOperation
resp, err = b.HandleRequest(context.Background(), req)
assert.False(t, resp.IsError())
assert.Nil(t, err)
for k, v := range tt.expectedResp {
assert.Equal(t, v, resp.Data[k])
}
// Delete the role
req.Operation = logical.DeleteOperation
resp, err = b.HandleRequest(context.Background(), req)
assert.False(t, resp.IsError())
assert.Nil(t, err)
})
}
}
func TestBackend_StaticRole_Config(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to db backend")
}
defer b.Cleanup(context.Background())
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
defer cleanup()
// create the database user
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
// Configure a connection
data := map[string]interface{}{
"connection_url": connURL,
"plugin_name": "postgresql-database-plugin",
"verify_connection": false,
"allowed_roles": []string{"plugin-role-test"},
"name": "plugin-test",
}
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: data,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Test static role creation scenarios. Uses a map, so there is no guaranteed
// ordering, so each case cleans up by deleting the role
testCases := map[string]struct {
account map[string]interface{}
path string
expected map[string]interface{}
err error
}{
"basic": {
account: map[string]interface{}{
"username": dbUser,
"rotation_period": "5400s",
},
path: "plugin-role-test",
expected: map[string]interface{}{
"username": dbUser,
"rotation_period": float64(5400),
},
},
"missing rotation period": {
account: map[string]interface{}{
"username": dbUser,
},
path: "plugin-role-test",
err: errors.New("rotation_period is required to create static accounts"),
},
"disallowed role config": {
account: map[string]interface{}{
"username": dbUser,
"rotation_period": "5400s",
},
path: "disallowed-role",
err: errors.New("\"disallowed-role\" is not an allowed role"),
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
data := map[string]interface{}{
"name": "plugin-role-test",
"db_name": "plugin-test",
"rotation_statements": testRoleStaticUpdate,
}
for k, v := range tc.account {
data[k] = v
}
path := "static-roles/" + tc.path
req := &logical.Request{
Operation: logical.CreateOperation,
Path: path,
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
if tc.err == nil {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
if err != nil && tc.err.Error() == err.Error() {
// errors match
return
}
if err == nil && tc.err.Error() == resp.Error().Error() {
// errors match
return
}
t.Fatalf("expected err message: (%s), got (%s), response error: (%s)", tc.err, err, resp.Error())
}
if tc.err != nil {
if err == nil || (resp == nil || !resp.IsError()) {
t.Fatal("expected error, got none")
}
}
// Read the role
data = map[string]interface{}{}
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "static-roles/plugin-role-test",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
expected := tc.expected
actual := make(map[string]interface{})
dataKeys := []string{"username", "password", "last_vault_rotation", "rotation_period"}
for _, key := range dataKeys {
if v, ok := resp.Data[key]; ok {
actual[key] = v
}
}
if len(tc.expected) > 0 {
// verify a password is returned, but we don't care what it's value is
if actual["password"] == "" {
t.Fatalf("expected result to contain password, but none found")
}
if v, ok := actual["last_vault_rotation"].(time.Time); !ok {
t.Fatalf("expected last_vault_rotation to be set to time.Time type, got: %#v", v)
}
// delete these values before the comparison, since we can't know them in
// advance
delete(actual, "password")
delete(actual, "last_vault_rotation")
if diff := deep.Equal(expected, actual); diff != nil {
t.Fatal(diff)
}
}
if len(tc.expected) == 0 && resp.Data["static_account"] != nil {
t.Fatalf("got unexpected static_account info: %#v", actual)
}
if diff := deep.Equal(resp.Data["db_name"], "plugin-test"); diff != nil {
t.Fatal(diff)
}
// Delete role for next run
req = &logical.Request{
Operation: logical.DeleteOperation,
Path: "static-roles/plugin-role-test",
Storage: config.StorageView,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
})
}
}
func TestBackend_StaticRole_Updates(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to db backend")
}
defer b.Cleanup(context.Background())
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
defer cleanup()
// create the database user
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
// Configure a connection
data := map[string]interface{}{
"connection_url": connURL,
"plugin_name": "postgresql-database-plugin",
"verify_connection": false,
"allowed_roles": []string{"*"},
"name": "plugin-test",
}
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: data,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
data = map[string]interface{}{
"name": "plugin-role-test-updates",
"db_name": "plugin-test",
"rotation_statements": testRoleStaticUpdate,
"username": dbUser,
"rotation_period": "5400s",
}
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "static-roles/plugin-role-test-updates",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Read the role
data = map[string]interface{}{}
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "static-roles/plugin-role-test-updates",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
rotation := resp.Data["rotation_period"].(float64)
// capture the password to verify it doesn't change
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "static-creds/plugin-role-test-updates",
Storage: config.StorageView,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
username := resp.Data["username"].(string)
password := resp.Data["password"].(string)
if username == "" || password == "" {
t.Fatalf("expected both username/password, got (%s), (%s)", username, password)
}
// update rotation_period
updateData := map[string]interface{}{
"name": "plugin-role-test-updates",
"db_name": "plugin-test",
"username": dbUser,
"rotation_period": "6400s",
}
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "static-roles/plugin-role-test-updates",
Storage: config.StorageView,
Data: updateData,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// re-read the role
data = map[string]interface{}{}
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "static-roles/plugin-role-test-updates",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
newRotation := resp.Data["rotation_period"].(float64)
if newRotation == rotation {
t.Fatalf("expected change in rotation, but got old value: %#v", newRotation)
}
// re-capture the password to ensure it did not change
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "static-creds/plugin-role-test-updates",
Storage: config.StorageView,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
if username != resp.Data["username"].(string) {
t.Fatalf("usernames dont match!: (%s) / (%s)", username, resp.Data["username"].(string))
}
if password != resp.Data["password"].(string) {
t.Fatalf("passwords dont match!: (%s) / (%s)", password, resp.Data["password"].(string))
}
// verify that rotation_period is only required when creating
updateData = map[string]interface{}{
"name": "plugin-role-test-updates",
"db_name": "plugin-test",
"username": dbUser,
"rotation_statements": testRoleStaticUpdateRotation,
}
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "static-roles/plugin-role-test-updates",
Storage: config.StorageView,
Data: updateData,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// verify updating static username returns an error
updateData = map[string]interface{}{
"name": "plugin-role-test-updates",
"db_name": "plugin-test",
"username": "statictestmodified",
}
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "static-roles/plugin-role-test-updates",
Storage: config.StorageView,
Data: updateData,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || !resp.IsError() {
t.Fatal("expected error on updating name")
}
err = resp.Error()
if err.Error() != "cannot update static account username" {
t.Fatalf("expected error on updating name, got: %s", err)
}
}
func TestBackend_StaticRole_Role_name_check(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to db backend")
}
defer b.Cleanup(context.Background())
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
defer cleanup()
// create the database user
createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate)
// Configure a connection
data := map[string]interface{}{
"connection_url": connURL,
"plugin_name": "postgresql-database-plugin",
"verify_connection": false,
"allowed_roles": []string{"*"},
"name": "plugin-test",
}
req := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: data,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// non-static role
data = map[string]interface{}{
"name": "plugin-role-test",
"db_name": "plugin-test",
"creation_statements": testRoleStaticCreate,
"rotation_statements": testRoleStaticUpdate,
"revocation_statements": defaultRevocationSQL,
"default_ttl": "5m",
"max_ttl": "10m",
}
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "roles/plugin-role-test",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// create a static role with the same name, and expect failure
// static role
data = map[string]interface{}{
"name": "plugin-role-test",
"db_name": "plugin-test",
"creation_statements": testRoleStaticCreate,
"rotation_statements": testRoleStaticUpdate,
"revocation_statements": defaultRevocationSQL,
}
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "static-roles/plugin-role-test",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected error, got none")
}
// repeat, with a static role first
data = map[string]interface{}{
"name": "plugin-role-test-2",
"db_name": "plugin-test",
"rotation_statements": testRoleStaticUpdate,
"username": dbUser,
"rotation_period": "1h",
}
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "static-roles/plugin-role-test-2",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// create a non-static role with the same name, and expect failure
data = map[string]interface{}{
"name": "plugin-role-test-2",
"db_name": "plugin-test",
"creation_statements": testRoleStaticCreate,
"revocation_statements": defaultRevocationSQL,
"default_ttl": "5m",
"max_ttl": "10m",
}
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "roles/plugin-role-test-2",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected error, got none")
}
}
func TestWALsStillTrackedAfterUpdate(t *testing.T) {
ctx := context.Background()
b, storage, mockDB := getBackend(t)
defer b.Cleanup(ctx)
configureDBMount(t, storage)
createRole(t, b, storage, mockDB, "hashicorp")
generateWALFromFailedRotation(t, b, storage, mockDB, "hashicorp")
requireWALs(t, storage, 1)
resp, err := b.HandleRequest(ctx, &logical.Request{
Operation: logical.UpdateOperation,
Path: "static-roles/hashicorp",
Storage: storage,
Data: map[string]interface{}{
"username": "hashicorp",
"rotation_period": "600s",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatal(resp, err)
}
walIDs := requireWALs(t, storage, 1)
// Now when we trigger a manual rotate, it should use the WAL's new password
// which will tell us that the in-memory structure still kept track of the
// WAL in addition to it still being in storage.
wal, err := b.findStaticWAL(ctx, storage, walIDs[0])
if err != nil {
t.Fatal(err)
}
rotateRole(t, b, storage, mockDB, "hashicorp")
role, err := b.StaticRole(ctx, storage, "hashicorp")
if err != nil {
t.Fatal(err)
}
if role.StaticAccount.Password != wal.NewPassword {
t.Fatal()
}
requireWALs(t, storage, 0)
}
func TestWALsDeletedOnRoleCreationFailed(t *testing.T) {
ctx := context.Background()
b, storage, mockDB := getBackend(t)
defer b.Cleanup(ctx)
configureDBMount(t, storage)
for i := 0; i < 3; i++ {
mockDB.On("UpdateUser", mock.Anything, mock.Anything).
Return(v5.UpdateUserResponse{}, errors.New("forced error")).
Once()
resp, err := b.HandleRequest(ctx, &logical.Request{
Operation: logical.CreateOperation,
Path: "static-roles/hashicorp",
Storage: storage,
Data: map[string]interface{}{
"username": "hashicorp",
"db_name": "mockv5",
"rotation_period": "5s",
},
})
if err == nil {
t.Fatal("expected error from DB")
}
if !strings.Contains(err.Error(), "forced error") {
t.Fatal("expected forced error message", resp, err)
}
}
requireWALs(t, storage, 0)
}
func TestWALsDeletedOnRoleDeletion(t *testing.T) {
ctx := context.Background()
b, storage, mockDB := getBackend(t)
defer b.Cleanup(ctx)
configureDBMount(t, storage)
// Create the roles
roleNames := []string{"hashicorp", "2"}
for _, roleName := range roleNames {
createRole(t, b, storage, mockDB, roleName)
}
// Fail to rotate the roles
for _, roleName := range roleNames {
generateWALFromFailedRotation(t, b, storage, mockDB, roleName)
}
// Should have 2 WALs hanging around
requireWALs(t, storage, 2)
// Delete one of the static roles
resp, err := b.HandleRequest(ctx, &logical.Request{
Operation: logical.DeleteOperation,
Path: "static-roles/hashicorp",
Storage: storage,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatal(resp, err)
}
// 1 WAL should be cleared by the delete
requireWALs(t, storage, 1)
}
func createRole(t *testing.T, b *databaseBackend, storage logical.Storage, mockDB *mockNewDatabase, roleName string) {
t.Helper()
mockDB.On("UpdateUser", mock.Anything, mock.Anything).
Return(v5.UpdateUserResponse{}, nil).
Once()
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.CreateOperation,
Path: "static-roles/" + roleName,
Storage: storage,
Data: map[string]interface{}{
"username": roleName,
"db_name": "mockv5",
"rotation_period": "86400s",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatal(resp, err)
}
}
const testRoleStaticCreate = `
CREATE ROLE "{{name}}" WITH
LOGIN
PASSWORD '{{password}}';
`
const testRoleStaticUpdate = `
ALTER USER "{{name}}" WITH PASSWORD '{{password}}';
`
const testRoleStaticUpdateRotation = `
ALTER USER "{{name}}" WITH PASSWORD '{{password}}';GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
`

View File

@ -1,425 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/helper/namespace"
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const (
databaseUser = "postgres"
defaultPassword = "secret"
)
// Tests that the WAL rollback function rolls back the database password.
// The database password should be rolled back when:
// - A WAL entry exists
// - Password has been altered on the database
// - Password has not been updated in storage
func TestBackend_RotateRootCredentials_WAL_rollback(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
dbBackend, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to db backend")
}
defer lb.Cleanup(context.Background())
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
defer cleanup()
connURL = strings.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}")
// Configure a connection to the database
data := map[string]interface{}{
"connection_url": connURL,
"plugin_name": "postgresql-database-plugin",
"allowed_roles": []string{"plugin-role-test"},
"username": databaseUser,
"password": defaultPassword,
}
resp, err := lb.HandleRequest(namespace.RootContext(nil), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: data,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Create a role
data = map[string]interface{}{
"db_name": "plugin-test",
"creation_statements": testRole,
"max_ttl": "10m",
}
resp, err = lb.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/plugin-role-test",
Storage: config.StorageView,
Data: data,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Read credentials to verify this initially works
credReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/plugin-role-test",
Storage: config.StorageView,
Data: make(map[string]interface{}),
}
credResp, err := lb.HandleRequest(context.Background(), credReq)
if err != nil || (credResp != nil && credResp.IsError()) {
t.Fatalf("err:%s resp:%v\n", err, credResp)
}
// Get a connection to the database plugin
dbi, err := dbBackend.GetConnection(context.Background(),
config.StorageView, "plugin-test")
if err != nil {
t.Fatal(err)
}
// Alter the database password so it no longer matches what is in storage
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
updateReq := v5.UpdateUserRequest{
Username: databaseUser,
Password: &v5.ChangePassword{
NewPassword: "newSecret",
},
}
_, err = dbi.database.UpdateUser(ctx, updateReq, false)
if err != nil {
t.Fatal(err)
}
// Clear the plugin connection to verify we're no longer able to connect
err = dbBackend.ClearConnection("plugin-test")
if err != nil {
t.Fatal(err)
}
// Reading credentials should no longer work
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
if err == nil {
t.Fatalf("expected authentication to fail when reading credentials")
}
// Put a WAL entry that will be used for rolling back the database password
walEntry := &rotateRootCredentialsWAL{
ConnectionName: "plugin-test",
UserName: databaseUser,
OldPassword: defaultPassword,
NewPassword: "newSecret",
}
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
if err != nil {
t.Fatal(err)
}
assertWALCount(t, config.StorageView, 1, rotateRootWALKey)
// Trigger an immediate RollbackOperation so that the WAL rollback
// function can use the WAL entry to roll back the database password
_, err = lb.HandleRequest(context.Background(), &logical.Request{
Operation: logical.RollbackOperation,
Path: "",
Storage: config.StorageView,
Data: map[string]interface{}{
"immediate": true,
},
})
if err != nil {
t.Fatal(err)
}
assertWALCount(t, config.StorageView, 0, rotateRootWALKey)
// Reading credentials should work again after the database
// password has been rolled back.
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
if err != nil || (credResp != nil && credResp.IsError()) {
t.Fatalf("err:%s resp:%v\n", err, credResp)
}
}
// Tests that the WAL rollback function does not roll back the database password.
// The database password should not be rolled back when:
// - A WAL entry exists
// - Password has not been altered on the database
// - Password has not been updated in storage
func TestBackend_RotateRootCredentials_WAL_no_rollback_1(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
defer lb.Cleanup(context.Background())
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
defer cleanup()
connURL = strings.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}")
// Configure a connection to the database
data := map[string]interface{}{
"connection_url": connURL,
"plugin_name": "postgresql-database-plugin",
"allowed_roles": []string{"plugin-role-test"},
"username": databaseUser,
"password": defaultPassword,
}
resp, err := lb.HandleRequest(namespace.RootContext(nil), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: data,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Create a role
data = map[string]interface{}{
"db_name": "plugin-test",
"creation_statements": testRole,
"max_ttl": "10m",
}
resp, err = lb.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/plugin-role-test",
Storage: config.StorageView,
Data: data,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Read credentials to verify this initially works
credReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/plugin-role-test",
Storage: config.StorageView,
Data: make(map[string]interface{}),
}
credResp, err := lb.HandleRequest(context.Background(), credReq)
if err != nil || (credResp != nil && credResp.IsError()) {
t.Fatalf("err:%s resp:%v\n", err, credResp)
}
// Put a WAL entry
walEntry := &rotateRootCredentialsWAL{
ConnectionName: "plugin-test",
UserName: databaseUser,
OldPassword: defaultPassword,
NewPassword: "newSecret",
}
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
if err != nil {
t.Fatal(err)
}
assertWALCount(t, config.StorageView, 1, rotateRootWALKey)
// Trigger an immediate RollbackOperation
_, err = lb.HandleRequest(context.Background(), &logical.Request{
Operation: logical.RollbackOperation,
Path: "",
Storage: config.StorageView,
Data: map[string]interface{}{
"immediate": true,
},
})
if err != nil {
t.Fatal(err)
}
assertWALCount(t, config.StorageView, 0, rotateRootWALKey)
// Reading credentials should work
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
if err != nil || (credResp != nil && credResp.IsError()) {
t.Fatalf("err:%s resp:%v\n", err, credResp)
}
}
// Tests that the WAL rollback function does not roll back the database password.
// The database password should not be rolled back when:
// - A WAL entry exists
// - Password has been altered on the database
// - Password has been updated in storage
func TestBackend_RotateRootCredentials_WAL_no_rollback_2(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
dbBackend, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to db backend")
}
defer lb.Cleanup(context.Background())
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
defer cleanup()
connURL = strings.ReplaceAll(connURL, "postgres:secret", "{{username}}:{{password}}")
// Configure a connection to the database
data := map[string]interface{}{
"connection_url": connURL,
"plugin_name": "postgresql-database-plugin",
"allowed_roles": []string{"plugin-role-test"},
"username": databaseUser,
"password": defaultPassword,
}
resp, err := lb.HandleRequest(namespace.RootContext(nil), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/plugin-test",
Storage: config.StorageView,
Data: data,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Create a role
data = map[string]interface{}{
"db_name": "plugin-test",
"creation_statements": testRole,
"max_ttl": "10m",
}
resp, err = lb.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/plugin-role-test",
Storage: config.StorageView,
Data: data,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
// Read credentials to verify this initially works
credReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/plugin-role-test",
Storage: config.StorageView,
Data: make(map[string]interface{}),
}
credResp, err := lb.HandleRequest(context.Background(), credReq)
if err != nil || (credResp != nil && credResp.IsError()) {
t.Fatalf("err:%s resp:%v\n", err, credResp)
}
// Get a connection to the database plugin
dbi, err := dbBackend.GetConnection(context.Background(), config.StorageView, "plugin-test")
if err != nil {
t.Fatal(err)
}
// Alter the database password
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
updateReq := v5.UpdateUserRequest{
Username: databaseUser,
Password: &v5.ChangePassword{
NewPassword: "newSecret",
},
}
_, err = dbi.database.UpdateUser(ctx, updateReq, false)
if err != nil {
t.Fatal(err)
}
// Update storage with the new password
dbConfig, err := dbBackend.DatabaseConfig(context.Background(), config.StorageView,
"plugin-test")
if err != nil {
t.Fatal(err)
}
dbConfig.ConnectionDetails["password"] = "newSecret"
entry, err := logical.StorageEntryJSON("config/plugin-test", dbConfig)
if err != nil {
t.Fatal(err)
}
err = config.StorageView.Put(context.Background(), entry)
if err != nil {
t.Fatal(err)
}
// Clear the plugin connection to verify we can connect to the database
err = dbBackend.ClearConnection("plugin-test")
if err != nil {
t.Fatal(err)
}
// Reading credentials should work
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
if err != nil || (credResp != nil && credResp.IsError()) {
t.Fatalf("err:%s resp:%v\n", err, credResp)
}
// Put a WAL entry
walEntry := &rotateRootCredentialsWAL{
ConnectionName: "plugin-test",
UserName: databaseUser,
OldPassword: defaultPassword,
NewPassword: "newSecret",
}
_, err = framework.PutWAL(context.Background(), config.StorageView, rotateRootWALKey, walEntry)
if err != nil {
t.Fatal(err)
}
assertWALCount(t, config.StorageView, 1, rotateRootWALKey)
// Trigger an immediate RollbackOperation
_, err = lb.HandleRequest(context.Background(), &logical.Request{
Operation: logical.RollbackOperation,
Path: "",
Storage: config.StorageView,
Data: map[string]interface{}{
"immediate": true,
},
})
if err != nil {
t.Fatal(err)
}
assertWALCount(t, config.StorageView, 0, rotateRootWALKey)
// Reading credentials should work
credResp, err = lb.HandleRequest(namespace.RootContext(nil), credReq)
if err != nil || (credResp != nil && credResp.IsError()) {
t.Fatalf("err:%s resp:%v\n", err, credResp)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,853 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"fmt"
"reflect"
"testing"
"time"
v4 "github.com/hashicorp/vault/sdk/database/dbplugin"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestInitDatabase_missingDB(t *testing.T) {
dbw := databaseVersionWrapper{}
req := v5.InitializeRequest{}
resp, err := dbw.Initialize(context.Background(), req)
if err == nil {
t.Fatalf("err expected, got nil")
}
expectedResp := v5.InitializeResponse{}
if !reflect.DeepEqual(resp, expectedResp) {
t.Fatalf("Actual resp: %#v\nExpected resp: %#v", resp, expectedResp)
}
}
func TestInitDatabase_newDB(t *testing.T) {
type testCase struct {
req v5.InitializeRequest
newInitResp v5.InitializeResponse
newInitErr error
newInitCalls int
expectedResp v5.InitializeResponse
expectErr bool
}
tests := map[string]testCase{
"success": {
req: v5.InitializeRequest{
Config: map[string]interface{}{
"foo": "bar",
},
VerifyConnection: true,
},
newInitResp: v5.InitializeResponse{
Config: map[string]interface{}{
"foo": "bar",
},
},
newInitCalls: 1,
expectedResp: v5.InitializeResponse{
Config: map[string]interface{}{
"foo": "bar",
},
},
expectErr: false,
},
"error": {
req: v5.InitializeRequest{
Config: map[string]interface{}{
"foo": "bar",
},
VerifyConnection: true,
},
newInitResp: v5.InitializeResponse{},
newInitErr: fmt.Errorf("test error"),
newInitCalls: 1,
expectedResp: v5.InitializeResponse{},
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
newDB := new(mockNewDatabase)
newDB.On("Initialize", mock.Anything, mock.Anything).
Return(test.newInitResp, test.newInitErr)
defer newDB.AssertNumberOfCalls(t, "Initialize", test.newInitCalls)
dbw := databaseVersionWrapper{
v5: newDB,
}
resp, err := dbw.Initialize(context.Background(), test.req)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(resp, test.expectedResp) {
t.Fatalf("Actual resp: %#v\nExpected resp: %#v", resp, test.expectedResp)
}
})
}
}
func TestInitDatabase_legacyDB(t *testing.T) {
type testCase struct {
req v5.InitializeRequest
initConfig map[string]interface{}
initErr error
initCalls int
expectedResp v5.InitializeResponse
expectErr bool
}
tests := map[string]testCase{
"success": {
req: v5.InitializeRequest{
Config: map[string]interface{}{
"foo": "bar",
},
VerifyConnection: true,
},
initConfig: map[string]interface{}{
"foo": "bar",
},
initCalls: 1,
expectedResp: v5.InitializeResponse{
Config: map[string]interface{}{
"foo": "bar",
},
},
expectErr: false,
},
"error": {
req: v5.InitializeRequest{
Config: map[string]interface{}{
"foo": "bar",
},
VerifyConnection: true,
},
initErr: fmt.Errorf("test error"),
initCalls: 1,
expectedResp: v5.InitializeResponse{},
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
legacyDB := new(mockLegacyDatabase)
legacyDB.On("Init", mock.Anything, mock.Anything, mock.Anything).
Return(test.initConfig, test.initErr)
defer legacyDB.AssertNumberOfCalls(t, "Init", test.initCalls)
dbw := databaseVersionWrapper{
v4: legacyDB,
}
resp, err := dbw.Initialize(context.Background(), test.req)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(resp, test.expectedResp) {
t.Fatalf("Actual resp: %#v\nExpected resp: %#v", resp, test.expectedResp)
}
})
}
}
func TestNewUser_missingDB(t *testing.T) {
dbw := databaseVersionWrapper{}
req := v5.NewUserRequest{}
resp, pass, err := dbw.NewUser(context.Background(), req)
if err == nil {
t.Fatalf("err expected, got nil")
}
expectedResp := v5.NewUserResponse{}
if !reflect.DeepEqual(resp, expectedResp) {
t.Fatalf("Actual resp: %#v\nExpected resp: %#v", resp, expectedResp)
}
if pass != "" {
t.Fatalf("Password should be empty but was: %s", pass)
}
}
func TestNewUser_newDB(t *testing.T) {
type testCase struct {
req v5.NewUserRequest
newUserResp v5.NewUserResponse
newUserErr error
newUserCalls int
expectedResp v5.NewUserResponse
expectErr bool
}
tests := map[string]testCase{
"success": {
req: v5.NewUserRequest{
Password: "new_password",
},
newUserResp: v5.NewUserResponse{
Username: "newuser",
},
newUserCalls: 1,
expectedResp: v5.NewUserResponse{
Username: "newuser",
},
expectErr: false,
},
"error": {
req: v5.NewUserRequest{
Password: "new_password",
},
newUserErr: fmt.Errorf("test error"),
newUserCalls: 1,
expectedResp: v5.NewUserResponse{},
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
newDB := new(mockNewDatabase)
newDB.On("NewUser", mock.Anything, mock.Anything).
Return(test.newUserResp, test.newUserErr)
defer newDB.AssertNumberOfCalls(t, "NewUser", test.newUserCalls)
dbw := databaseVersionWrapper{
v5: newDB,
}
resp, password, err := dbw.NewUser(context.Background(), test.req)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(resp, test.expectedResp) {
t.Fatalf("Actual resp: %#v\nExpected resp: %#v", resp, test.expectedResp)
}
if password != test.req.Password {
t.Fatalf("Actual password: %s Expected password: %s", password, test.req.Password)
}
})
}
}
func TestNewUser_legacyDB(t *testing.T) {
type testCase struct {
req v5.NewUserRequest
createUserUsername string
createUserPassword string
createUserErr error
createUserCalls int
expectedResp v5.NewUserResponse
expectedPassword string
expectErr bool
}
tests := map[string]testCase{
"success": {
req: v5.NewUserRequest{
Password: "new_password",
},
createUserUsername: "newuser",
createUserPassword: "securepassword",
createUserCalls: 1,
expectedResp: v5.NewUserResponse{
Username: "newuser",
},
expectedPassword: "securepassword",
expectErr: false,
},
"error": {
req: v5.NewUserRequest{
Password: "new_password",
},
createUserErr: fmt.Errorf("test error"),
createUserCalls: 1,
expectedResp: v5.NewUserResponse{},
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
legacyDB := new(mockLegacyDatabase)
legacyDB.On("CreateUser", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(test.createUserUsername, test.createUserPassword, test.createUserErr)
defer legacyDB.AssertNumberOfCalls(t, "CreateUser", test.createUserCalls)
dbw := databaseVersionWrapper{
v4: legacyDB,
}
resp, password, err := dbw.NewUser(context.Background(), test.req)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(resp, test.expectedResp) {
t.Fatalf("Actual resp: %#v\nExpected resp: %#v", resp, test.expectedResp)
}
if password != test.expectedPassword {
t.Fatalf("Actual password: %s Expected password: %s", password, test.req.Password)
}
})
}
}
func TestUpdateUser_missingDB(t *testing.T) {
dbw := databaseVersionWrapper{}
req := v5.UpdateUserRequest{}
resp, err := dbw.UpdateUser(context.Background(), req, false)
if err == nil {
t.Fatalf("err expected, got nil")
}
expectedConfig := map[string]interface{}(nil)
if !reflect.DeepEqual(resp, expectedConfig) {
t.Fatalf("Actual config: %#v\nExpected config: %#v", resp, expectedConfig)
}
}
func TestUpdateUser_newDB(t *testing.T) {
type testCase struct {
req v5.UpdateUserRequest
updateUserErr error
updateUserCalls int
expectedResp v5.UpdateUserResponse
expectErr bool
}
tests := map[string]testCase{
"success": {
req: v5.UpdateUserRequest{
Username: "existing_user",
},
updateUserCalls: 1,
expectErr: false,
},
"error": {
req: v5.UpdateUserRequest{
Username: "existing_user",
},
updateUserErr: fmt.Errorf("test error"),
updateUserCalls: 1,
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
newDB := new(mockNewDatabase)
newDB.On("UpdateUser", mock.Anything, mock.Anything).
Return(v5.UpdateUserResponse{}, test.updateUserErr)
defer newDB.AssertNumberOfCalls(t, "UpdateUser", test.updateUserCalls)
dbw := databaseVersionWrapper{
v5: newDB,
}
_, err := dbw.UpdateUser(context.Background(), test.req, false)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
})
}
}
func TestUpdateUser_legacyDB(t *testing.T) {
type testCase struct {
req v5.UpdateUserRequest
isRootUser bool
setCredentialsErr error
setCredentialsCalls int
rotateRootConfig map[string]interface{}
rotateRootErr error
rotateRootCalls int
renewUserErr error
renewUserCalls int
expectedConfig map[string]interface{}
expectErr bool
}
tests := map[string]testCase{
"missing changes": {
req: v5.UpdateUserRequest{
Username: "existing_user",
},
isRootUser: false,
setCredentialsCalls: 0,
rotateRootCalls: 0,
renewUserCalls: 0,
expectErr: true,
},
"both password and expiration changes": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Password: &v5.ChangePassword{},
Expiration: &v5.ChangeExpiration{},
},
isRootUser: false,
setCredentialsCalls: 0,
rotateRootCalls: 0,
renewUserCalls: 0,
expectErr: true,
},
"change password - SetCredentials": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Password: &v5.ChangePassword{
NewPassword: "newpassowrd",
},
},
isRootUser: false,
setCredentialsErr: nil,
setCredentialsCalls: 1,
rotateRootCalls: 0,
renewUserCalls: 0,
expectedConfig: nil,
expectErr: false,
},
"change password - SetCredentials failed": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Password: &v5.ChangePassword{
NewPassword: "newpassowrd",
},
},
isRootUser: false,
setCredentialsErr: fmt.Errorf("set credentials failed"),
setCredentialsCalls: 1,
rotateRootCalls: 0,
renewUserCalls: 0,
expectedConfig: nil,
expectErr: true,
},
"change password - SetCredentials unimplemented but not a root user": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Password: &v5.ChangePassword{
NewPassword: "newpassowrd",
},
},
isRootUser: false,
setCredentialsErr: status.Error(codes.Unimplemented, "SetCredentials is not implemented"),
setCredentialsCalls: 1,
rotateRootCalls: 0,
renewUserCalls: 0,
expectedConfig: nil,
expectErr: true,
},
"change password - RotateRootCredentials (gRPC Unimplemented)": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Password: &v5.ChangePassword{
NewPassword: "newpassowrd",
},
},
isRootUser: true,
setCredentialsErr: status.Error(codes.Unimplemented, "SetCredentials is not implemented"),
setCredentialsCalls: 1,
rotateRootConfig: map[string]interface{}{
"foo": "bar",
},
rotateRootCalls: 1,
renewUserCalls: 0,
expectedConfig: map[string]interface{}{
"foo": "bar",
},
expectErr: false,
},
"change password - RotateRootCredentials (ErrPluginStaticUnsupported)": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Password: &v5.ChangePassword{
NewPassword: "newpassowrd",
},
},
isRootUser: true,
setCredentialsErr: v4.ErrPluginStaticUnsupported,
setCredentialsCalls: 1,
rotateRootConfig: map[string]interface{}{
"foo": "bar",
},
rotateRootCalls: 1,
renewUserCalls: 0,
expectedConfig: map[string]interface{}{
"foo": "bar",
},
expectErr: false,
},
"change password - RotateRootCredentials failed": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Password: &v5.ChangePassword{
NewPassword: "newpassowrd",
},
},
isRootUser: true,
setCredentialsErr: status.Error(codes.Unimplemented, "SetCredentials is not implemented"),
setCredentialsCalls: 1,
rotateRootErr: fmt.Errorf("rotate root failed"),
rotateRootCalls: 1,
renewUserCalls: 0,
expectedConfig: nil,
expectErr: true,
},
"change expiration": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Expiration: &v5.ChangeExpiration{
NewExpiration: time.Now(),
},
},
isRootUser: false,
setCredentialsCalls: 0,
rotateRootCalls: 0,
renewUserErr: nil,
renewUserCalls: 1,
expectedConfig: nil,
expectErr: false,
},
"change expiration failed": {
req: v5.UpdateUserRequest{
Username: "existing_user",
Expiration: &v5.ChangeExpiration{
NewExpiration: time.Now(),
},
},
isRootUser: false,
setCredentialsCalls: 0,
rotateRootCalls: 0,
renewUserErr: fmt.Errorf("test error"),
renewUserCalls: 1,
expectedConfig: nil,
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
legacyDB := new(mockLegacyDatabase)
legacyDB.On("SetCredentials", mock.Anything, mock.Anything, mock.Anything).
Return("", "", test.setCredentialsErr)
defer legacyDB.AssertNumberOfCalls(t, "SetCredentials", test.setCredentialsCalls)
legacyDB.On("RotateRootCredentials", mock.Anything, mock.Anything).
Return(test.rotateRootConfig, test.rotateRootErr)
defer legacyDB.AssertNumberOfCalls(t, "RotateRootCredentials", test.rotateRootCalls)
legacyDB.On("RenewUser", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(test.renewUserErr)
defer legacyDB.AssertNumberOfCalls(t, "RenewUser", test.renewUserCalls)
dbw := databaseVersionWrapper{
v4: legacyDB,
}
newConfig, err := dbw.UpdateUser(context.Background(), test.req, test.isRootUser)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(newConfig, test.expectedConfig) {
t.Fatalf("Actual config: %#v\nExpected config: %#v", newConfig, test.expectedConfig)
}
})
}
}
func TestDeleteUser_missingDB(t *testing.T) {
dbw := databaseVersionWrapper{}
req := v5.DeleteUserRequest{}
_, err := dbw.DeleteUser(context.Background(), req)
if err == nil {
t.Fatalf("err expected, got nil")
}
}
func TestDeleteUser_newDB(t *testing.T) {
type testCase struct {
req v5.DeleteUserRequest
deleteUserErr error
deleteUserCalls int
expectErr bool
}
tests := map[string]testCase{
"success": {
req: v5.DeleteUserRequest{
Username: "existing_user",
},
deleteUserErr: nil,
deleteUserCalls: 1,
expectErr: false,
},
"error": {
req: v5.DeleteUserRequest{
Username: "existing_user",
},
deleteUserErr: fmt.Errorf("test error"),
deleteUserCalls: 1,
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
newDB := new(mockNewDatabase)
newDB.On("DeleteUser", mock.Anything, mock.Anything).
Return(v5.DeleteUserResponse{}, test.deleteUserErr)
defer newDB.AssertNumberOfCalls(t, "DeleteUser", test.deleteUserCalls)
dbw := databaseVersionWrapper{
v5: newDB,
}
_, err := dbw.DeleteUser(context.Background(), test.req)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
})
}
}
func TestDeleteUser_legacyDB(t *testing.T) {
type testCase struct {
req v5.DeleteUserRequest
revokeUserErr error
revokeUserCalls int
expectErr bool
}
tests := map[string]testCase{
"success": {
req: v5.DeleteUserRequest{
Username: "existing_user",
},
revokeUserErr: nil,
revokeUserCalls: 1,
expectErr: false,
},
"error": {
req: v5.DeleteUserRequest{
Username: "existing_user",
},
revokeUserErr: fmt.Errorf("test error"),
revokeUserCalls: 1,
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
legacyDB := new(mockLegacyDatabase)
legacyDB.On("RevokeUser", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(test.revokeUserErr)
defer legacyDB.AssertNumberOfCalls(t, "RevokeUser", test.revokeUserCalls)
dbw := databaseVersionWrapper{
v4: legacyDB,
}
_, err := dbw.DeleteUser(context.Background(), test.req)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
})
}
}
type badValue struct{}
func (badValue) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("this value cannot be marshalled to JSON")
}
var _ logical.Storage = fakeStorage{}
type fakeStorage struct {
putErr error
}
func (f fakeStorage) Put(ctx context.Context, entry *logical.StorageEntry) error {
return f.putErr
}
func (f fakeStorage) List(ctx context.Context, s string) ([]string, error) {
panic("list not implemented")
}
func (f fakeStorage) Get(ctx context.Context, s string) (*logical.StorageEntry, error) {
panic("get not implemented")
}
func (f fakeStorage) Delete(ctx context.Context, s string) error {
panic("delete not implemented")
}
func TestStoreConfig(t *testing.T) {
type testCase struct {
config *DatabaseConfig
putErr error
expectErr bool
}
tests := map[string]testCase{
"bad config": {
config: &DatabaseConfig{
PluginName: "testplugin",
ConnectionDetails: map[string]interface{}{
"bad value": badValue{},
},
},
putErr: nil,
expectErr: true,
},
"storage error": {
config: &DatabaseConfig{
PluginName: "testplugin",
ConnectionDetails: map[string]interface{}{
"foo": "bar",
},
},
putErr: fmt.Errorf("failed to store config"),
expectErr: true,
},
"happy path": {
config: &DatabaseConfig{
PluginName: "testplugin",
ConnectionDetails: map[string]interface{}{
"foo": "bar",
},
},
putErr: nil,
expectErr: false,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
storage := fakeStorage{
putErr: test.putErr,
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := storeConfig(ctx, storage, "testconfig", test.config)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
})
}
}

View File

@ -1,532 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
// This file contains all "large"/expensive tests. These are running requests against a running backend
import (
"context"
"fmt"
"os"
"regexp"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)
func TestPlugin_lifecycle(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v4-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV4", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV5", []string{}, "")
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v6-database-plugin-muxed", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV6Multiplexed", []string{}, "")
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to database backend")
}
defer b.Cleanup(context.Background())
type testCase struct {
dbName string
dbType string
configData map[string]interface{}
assertDynamicUsername stringAssertion
assertDynamicPassword stringAssertion
}
tests := map[string]testCase{
"v4": {
dbName: "mockv4",
dbType: "mock-v4-database-plugin",
configData: map[string]interface{}{
"name": "mockv4",
"plugin_name": "mock-v4-database-plugin",
"connection_url": "sample_connection_url",
"verify_connection": true,
"allowed_roles": []string{"*"},
"username": "mockv4-user",
"password": "mysecurepassword",
},
assertDynamicUsername: assertStringPrefix("mockv4_user_"),
assertDynamicPassword: assertStringPrefix("mockv4_"),
},
"v5": {
dbName: "mockv5",
dbType: "mock-v5-database-plugin",
configData: map[string]interface{}{
"connection_url": "sample_connection_url",
"plugin_name": "mock-v5-database-plugin",
"verify_connection": true,
"allowed_roles": []string{"*"},
"name": "mockv5",
"username": "mockv5-user",
"password": "mysecurepassword",
},
assertDynamicUsername: assertStringPrefix("mockv5_user_"),
assertDynamicPassword: assertStringRegex("^[a-zA-Z0-9-]{20}"),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var cleanupReqs []*logical.Request
defer func() {
// Do not defer cleanup directly so that we can populate the
// slice before the function gets executed.
cleanup(t, b, cleanupReqs)
}()
// /////////////////////////////////////////////////////////////////
// Configure
req := &logical.Request{
Operation: logical.CreateOperation,
Path: fmt.Sprintf("config/%s", test.dbName),
Storage: config.StorageView,
Data: test.configData,
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
cleanupReqs = append(cleanupReqs, &logical.Request{
Operation: logical.DeleteOperation,
Path: fmt.Sprintf("config/%s", test.dbName),
Storage: config.StorageView,
})
// /////////////////////////////////////////////////////////////////
// Rotate root credentials
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: fmt.Sprintf("rotate-root/%s", test.dbName),
Storage: config.StorageView,
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err = b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
// /////////////////////////////////////////////////////////////////
// Dynamic credentials
// Create role
dynamicRoleName := "dynamic-role"
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: fmt.Sprintf("roles/%s", dynamicRoleName),
Storage: config.StorageView,
Data: map[string]interface{}{
"db_name": test.dbName,
"default_ttl": "5s",
"max_ttl": "1m",
},
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err = b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
cleanupReqs = append(cleanupReqs, &logical.Request{
Operation: logical.DeleteOperation,
Path: fmt.Sprintf("roles/%s", dynamicRoleName),
Storage: config.StorageView,
})
// Generate credentials
req = &logical.Request{
Operation: logical.ReadOperation,
Path: fmt.Sprintf("creds/%s", dynamicRoleName),
Storage: config.StorageView,
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err = b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertRespHasData(t, resp)
// TODO: Figure out how to make a call to the cluster that gives back a lease ID
// And also rotates the secret out after its TTL
// /////////////////////////////////////////////////////////////////
// Static credentials
// Create static role
staticRoleName := "static-role"
req = &logical.Request{
Operation: logical.CreateOperation,
Path: fmt.Sprintf("static-roles/%s", staticRoleName),
Storage: config.StorageView,
Data: map[string]interface{}{
"db_name": test.dbName,
"username": "static-username",
"rotation_period": "5",
},
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err = b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
cleanupReqs = append(cleanupReqs, &logical.Request{
Operation: logical.DeleteOperation,
Path: fmt.Sprintf("static-roles/%s", staticRoleName),
Storage: config.StorageView,
})
// Get credentials
req = &logical.Request{
Operation: logical.ReadOperation,
Path: fmt.Sprintf("static-creds/%s", staticRoleName),
Storage: config.StorageView,
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err = b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertRespHasData(t, resp)
})
}
}
func TestPlugin_VersionSelection(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
for _, version := range []string{"v11.0.0", "v11.0.1-rc1", "v2.0.0"} {
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, version, "TestBackend_PluginMain_MockV5", []string{}, "")
}
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to database backend")
}
defer b.Cleanup(context.Background())
test := func(t *testing.T, selectVersion, expectedVersion string) func(t *testing.T) {
return func(t *testing.T) {
req := &logical.Request{
Operation: logical.CreateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: map[string]interface{}{
"connection_url": "sample_connection_url",
"plugin_name": "mock-v5-database-plugin",
"plugin_version": selectVersion,
"verify_connection": true,
"allowed_roles": []string{"*"},
"name": "mockv5",
"username": "mockv5-user",
"password": "mysecurepassword",
},
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
defer func() {
_, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "config/db",
Storage: config.StorageView,
})
if err != nil {
t.Fatal(err)
}
}()
req = &logical.Request{
Operation: logical.ReadOperation,
Path: "config/db",
Storage: config.StorageView,
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err = b.HandleRequest(ctx, req)
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
if resp.Data["plugin_version"].(string) != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, resp.Data["plugin_version"].(string))
}
}
}
for name, tc := range map[string]struct {
selectVersion string
expectedVersion string
}{
"no version specified, selects latest in the absence of unversioned plugins": {
selectVersion: "",
expectedVersion: "v11.0.1-rc1",
},
"specific version selected": {
selectVersion: "11.0.0",
expectedVersion: "v11.0.0",
},
} {
t.Run(name, test(t, tc.selectVersion, tc.expectedVersion))
}
// Register a newer version of the plugin, and ensure that's the new default version selected.
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "v11.0.1", "TestBackend_PluginMain_MockV5", []string{}, "")
t.Run("no version specified, new latest version selected", test(t, "", "v11.0.1"))
// Register an unversioned plugin and ensure that is now selected when no version is specified.
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mock-v5-database-plugin", consts.PluginTypeDatabase, "", "TestBackend_PluginMain_MockV5", []string{}, "")
for name, tc := range map[string]struct {
selectVersion string
expectedVersion string
}{
"no version specified, selects unversioned": {
selectVersion: "",
expectedVersion: "",
},
"specific version selected": {
selectVersion: "v2.0.0",
expectedVersion: "v2.0.0",
},
} {
t.Run(name, test(t, tc.selectVersion, tc.expectedVersion))
}
}
func TestPlugin_VersionMustBeExplicitlyUpgraded(t *testing.T) {
cluster, sys := getCluster(t)
defer cluster.Cleanup()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
config.System = sys
lb, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
b, ok := lb.(*databaseBackend)
if !ok {
t.Fatal("could not convert to database backend")
}
defer b.Cleanup(context.Background())
configData := func(extraData ...string) map[string]interface{} {
data := map[string]interface{}{
"connection_url": "sample_connection_url",
"plugin_name": "mysql-database-plugin",
"verify_connection": false,
"allowed_roles": []string{"*"},
"username": "mockv5-user",
"password": "mysecurepassword",
}
if len(extraData)%2 != 0 {
t.Fatal("Expected an even number of args in extraData")
}
for i := 0; i < len(extraData); i += 2 {
data[extraData[i]] = extraData[i+1]
}
return data
}
readVersion := func() string {
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "config/db",
Storage: config.StorageView,
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
return resp.Data["plugin_version"].(string)
}
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.CreateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: configData(),
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
version := readVersion()
expectedVersion := ""
if version != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, version)
}
// Register versioned plugin, and check that a new write to existing config doesn't upgrade the plugin implicitly.
vault.TestAddTestPlugin(t, cluster.Cores[0].Core, "mysql-database-plugin", consts.PluginTypeDatabase, "v1.0.0", "TestBackend_PluginMain_MockV5", []string{}, "")
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: configData(),
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
version = readVersion()
if version != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, version)
}
// Now explicitly upgrade.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/db",
Storage: config.StorageView,
Data: configData("plugin_version", "1.0.0"),
})
assertErrIsNil(t, err)
assertRespHasNoErr(t, resp)
assertNoRespData(t, resp)
version = readVersion()
expectedVersion = "v1.0.0"
if version != expectedVersion {
t.Fatalf("Expected version %q but got %q", expectedVersion, version)
}
}
func cleanup(t *testing.T, b *databaseBackend, reqs []*logical.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Go in stack order so it works similar to defer
for i := len(reqs) - 1; i >= 0; i-- {
req := reqs[i]
resp, err := b.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("Error cleaning up: %s", err)
}
if resp != nil && resp.IsError() {
t.Fatalf("Error cleaning up: %s", resp.Error())
}
}
}
func TestBackend_PluginMain_MockV4(t *testing.T) {
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" {
return
}
caPEM := os.Getenv(pluginutil.PluginCACertPEMEnv)
if caPEM == "" {
t.Fatal("CA cert not passed in")
}
args := []string{"--ca-cert=" + caPEM}
apiClientMeta := &api.PluginAPIClientMeta{}
flags := apiClientMeta.FlagSet()
flags.Parse(args)
RunV4(apiClientMeta.GetTLSConfig())
}
func TestBackend_PluginMain_MockV5(t *testing.T) {
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
return
}
RunV5()
}
func TestBackend_PluginMain_MockV6Multiplexed(t *testing.T) {
if os.Getenv(pluginutil.PluginVaultVersionEnv) == "" {
return
}
RunV6Multiplexed()
}
func assertNoRespData(t *testing.T, resp *logical.Response) {
t.Helper()
if resp != nil && len(resp.Data) > 0 {
t.Fatalf("Response had data when none was expected: %#v", resp.Data)
}
}
func assertRespHasData(t *testing.T, resp *logical.Response) {
t.Helper()
if resp == nil || len(resp.Data) == 0 {
t.Fatalf("Response didn't have any data when some was expected")
}
}
type stringAssertion func(t *testing.T, str string)
func assertStringPrefix(expectedPrefix string) stringAssertion {
return func(t *testing.T, str string) {
t.Helper()
if !strings.HasPrefix(str, expectedPrefix) {
t.Fatalf("Missing prefix %q: Actual: %q", expectedPrefix, str)
}
}
}
func assertStringRegex(expectedRegex string) stringAssertion {
re := regexp.MustCompile(expectedRegex)
return func(t *testing.T, str string) {
if !re.MatchString(str) {
t.Fatalf("Actual: %q did not match regexp %q", str, expectedRegex)
}
}
}
func assertRespHasNoErr(t *testing.T, resp *logical.Response) {
t.Helper()
if resp != nil && resp.IsError() {
t.Fatalf("response is error: %#v\n", resp)
}
}
func assertErrIsNil(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("No error expected, got: %s", err)
}
}

View File

@ -1,708 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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/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) {
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-----`

View File

@ -1,322 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"strings"
"testing"
"time"
"golang.org/x/crypto/acme"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/logical/pki/dnstest"
"github.com/hashicorp/vault/helper/constants"
"github.com/hashicorp/vault/helper/timeutil"
"github.com/hashicorp/vault/vault"
"github.com/hashicorp/vault/vault/activity"
"github.com/stretchr/testify/require"
)
// TestACMEBilling is a basic test that will validate client counts created via ACME workflows.
func TestACMEBilling(t *testing.T) {
t.Parallel()
timeutil.SkipAtEndOfMonth(t)
cluster, client, _ := setupAcmeBackend(t)
defer cluster.Cleanup()
dns := dnstest.SetupResolver(t, "dadgarcorp.com")
defer dns.Cleanup()
// Enable additional mounts.
setupAcmeBackendOnClusterAtPath(t, cluster, client, "pki2")
setupAcmeBackendOnClusterAtPath(t, cluster, client, "ns1/pki")
setupAcmeBackendOnClusterAtPath(t, cluster, client, "ns2/pki")
// Enable custom DNS resolver for testing.
for _, mount := range []string{"pki", "pki2", "ns1/pki", "ns2/pki"} {
_, err := client.Logical().Write(mount+"/config/acme", map[string]interface{}{
"dns_resolver": dns.GetLocalAddr(),
})
require.NoError(t, err, "failed to set local dns resolver address for testing on mount: "+mount)
}
// Enable client counting.
_, err := client.Logical().Write("/sys/internal/counters/config", map[string]interface{}{
"enabled": "enable",
})
require.NoError(t, err, "failed to enable client counting")
// Setup ACME clients. We refresh account keys each time for consistency.
acmeClientPKI := getAcmeClientForCluster(t, cluster, "/v1/pki/acme/", nil)
acmeClientPKI2 := getAcmeClientForCluster(t, cluster, "/v1/pki2/acme/", nil)
acmeClientPKINS1 := getAcmeClientForCluster(t, cluster, "/v1/ns1/pki/acme/", nil)
acmeClientPKINS2 := getAcmeClientForCluster(t, cluster, "/v1/ns2/pki/acme/", nil)
// Get our initial count.
expectedCount := validateClientCount(t, client, "", -1, "initial fetch")
// Unique identifier: should increase by one.
doACMEForDomainWithDNS(t, dns, acmeClientPKI, []string{"dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "pki", expectedCount+1, "new certificate")
// Different identifier; should increase by one.
doACMEForDomainWithDNS(t, dns, acmeClientPKI, []string{"example.dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "pki", expectedCount+1, "new certificate")
// While same identifiers, used together and so thus are unique; increase by one.
doACMEForDomainWithDNS(t, dns, acmeClientPKI, []string{"example.dadgarcorp.com", "dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "pki", expectedCount+1, "new certificate")
// Same identifiers in different order are not unique; keep the same.
doACMEForDomainWithDNS(t, dns, acmeClientPKI, []string{"dadgarcorp.com", "example.dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "pki", expectedCount, "different order; same identifiers")
// Using a different mount shouldn't affect counts.
doACMEForDomainWithDNS(t, dns, acmeClientPKI2, []string{"dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "", expectedCount, "different mount; same identifiers")
// But using a different identifier should.
doACMEForDomainWithDNS(t, dns, acmeClientPKI2, []string{"pki2.dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "pki2", expectedCount+1, "different mount with different identifiers")
// A new identifier in a unique namespace will affect results.
doACMEForDomainWithDNS(t, dns, acmeClientPKINS1, []string{"unique.dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "ns1/pki", expectedCount+1, "unique identifier in a namespace")
// But in a different namespace with the existing identifier will not.
doACMEForDomainWithDNS(t, dns, acmeClientPKINS2, []string{"unique.dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "", expectedCount, "existing identifier in a namespace")
doACMEForDomainWithDNS(t, dns, acmeClientPKI2, []string{"unique.dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "", expectedCount, "existing identifier outside of a namespace")
// Creating a unique identifier in a namespace with a mount with the
// same name as another namespace should increase counts as well.
doACMEForDomainWithDNS(t, dns, acmeClientPKINS2, []string{"very-unique.dadgarcorp.com"})
expectedCount = validateClientCount(t, client, "ns2/pki", expectedCount+1, "unique identifier in a different namespace")
// Check the current fragment
fragment := cluster.Cores[0].Core.ResetActivityLog()[0]
if fragment == nil {
t.Fatal("no fragment created")
}
validateAcmeClientTypes(t, fragment, expectedCount)
}
func validateAcmeClientTypes(t *testing.T, fragment *activity.LogFragment, expectedCount int64) {
t.Helper()
if int64(len(fragment.Clients)) != expectedCount {
t.Fatalf("bad number of entities, expected %v: got %v, entities are: %v", expectedCount, len(fragment.Clients), fragment.Clients)
}
for _, ac := range fragment.Clients {
if ac.ClientType != vault.ACMEActivityType {
t.Fatalf("Couldn't find expected '%v' client_type in %v", vault.ACMEActivityType, fragment.Clients)
}
}
}
func validateClientCount(t *testing.T, client *api.Client, mount string, expected int64, message string) int64 {
resp, err := client.Logical().Read("/sys/internal/counters/activity/monthly")
require.NoError(t, err, "failed to fetch client count values")
t.Logf("got client count numbers: %v", resp)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.Contains(t, resp.Data, "non_entity_clients")
require.Contains(t, resp.Data, "months")
rawCount := resp.Data["non_entity_clients"].(json.Number)
count, err := rawCount.Int64()
require.NoError(t, err, "failed to parse number as int64: "+rawCount.String())
if expected != -1 {
require.Equal(t, expected, count, "value of client counts did not match expectations: "+message)
}
if mount == "" {
return count
}
months := resp.Data["months"].([]interface{})
if len(months) > 1 {
t.Fatalf("running across a month boundary despite using SkipAtEndOfMonth(...); rerun test from start fully in the next month instead")
}
require.Equal(t, 1, len(months), "expected only a single month when running this test")
monthlyInfo := months[0].(map[string]interface{})
// Validate this month's aggregate counts match the overall value.
require.Contains(t, monthlyInfo, "counts", "expected monthly info to contain a count key")
monthlyCounts := monthlyInfo["counts"].(map[string]interface{})
require.Contains(t, monthlyCounts, "non_entity_clients", "expected month[0].counts to contain a non_entity_clients key")
monthlyCountNonEntityRaw := monthlyCounts["non_entity_clients"].(json.Number)
monthlyCountNonEntity, err := monthlyCountNonEntityRaw.Int64()
require.NoError(t, err, "failed to parse number as int64: "+monthlyCountNonEntityRaw.String())
require.Equal(t, count, monthlyCountNonEntity, "expected equal values for non entity client counts")
// Validate this mount's namespace is included in the namespaces list,
// if this is enterprise. Otherwise, if its OSS or we don't have a
// namespace, we default to the value root.
mountNamespace := ""
mountPath := mount + "/"
if constants.IsEnterprise && strings.Contains(mount, "/") {
pieces := strings.Split(mount, "/")
require.Equal(t, 2, len(pieces), "we do not support nested namespaces in this test")
mountNamespace = pieces[0] + "/"
mountPath = pieces[1] + "/"
}
require.Contains(t, monthlyInfo, "namespaces", "expected monthly info to contain a namespaces key")
monthlyNamespaces := monthlyInfo["namespaces"].([]interface{})
foundNamespace := false
for index, namespaceRaw := range monthlyNamespaces {
namespace := namespaceRaw.(map[string]interface{})
require.Contains(t, namespace, "namespace_path", "expected monthly.namespaces[%v] to contain a namespace_path key", index)
namespacePath := namespace["namespace_path"].(string)
if namespacePath != mountNamespace {
t.Logf("skipping non-matching namespace %v: %v != %v / %v", index, namespacePath, mountNamespace, namespace)
continue
}
foundNamespace = true
// This namespace must have a non-empty aggregate non-entity count.
require.Contains(t, namespace, "counts", "expected monthly.namespaces[%v] to contain a counts key", index)
namespaceCounts := namespace["counts"].(map[string]interface{})
require.Contains(t, namespaceCounts, "non_entity_clients", "expected namespace counts to contain a non_entity_clients key")
namespaceCountNonEntityRaw := namespaceCounts["non_entity_clients"].(json.Number)
namespaceCountNonEntity, err := namespaceCountNonEntityRaw.Int64()
require.NoError(t, err, "failed to parse number as int64: "+namespaceCountNonEntityRaw.String())
require.Greater(t, namespaceCountNonEntity, int64(0), "expected at least one non-entity client count value in the namespace")
require.Contains(t, namespace, "mounts", "expected monthly.namespaces[%v] to contain a mounts key", index)
namespaceMounts := namespace["mounts"].([]interface{})
foundMount := false
for mountIndex, mountRaw := range namespaceMounts {
mountInfo := mountRaw.(map[string]interface{})
require.Contains(t, mountInfo, "mount_path", "expected monthly.namespaces[%v].mounts[%v] to contain a mount_path key", index, mountIndex)
mountInfoPath := mountInfo["mount_path"].(string)
if mountPath != mountInfoPath {
t.Logf("skipping non-matching mount path %v in namespace %v: %v != %v / %v of %v", mountIndex, index, mountPath, mountInfoPath, mountInfo, namespace)
continue
}
foundMount = true
// This mount must also have a non-empty non-entity client count.
require.Contains(t, mountInfo, "counts", "expected monthly.namespaces[%v].mounts[%v] to contain a counts key", index, mountIndex)
mountCounts := mountInfo["counts"].(map[string]interface{})
require.Contains(t, mountCounts, "non_entity_clients", "expected mount counts to contain a non_entity_clients key")
mountCountNonEntityRaw := mountCounts["non_entity_clients"].(json.Number)
mountCountNonEntity, err := mountCountNonEntityRaw.Int64()
require.NoError(t, err, "failed to parse number as int64: "+mountCountNonEntityRaw.String())
require.Greater(t, mountCountNonEntity, int64(0), "expected at least one non-entity client count value in the mount")
}
require.True(t, foundMount, "expected to find the mount "+mountPath+" in the list of mounts for namespace, but did not")
}
require.True(t, foundNamespace, "expected to find the namespace "+mountNamespace+" in the list of namespaces, but did not")
return count
}
func doACMEForDomainWithDNS(t *testing.T, dns *dnstest.TestServer, acmeClient *acme.Client, domains []string) *x509.Certificate {
cr := &x509.CertificateRequest{
Subject: pkix.Name{CommonName: domains[0]},
DNSNames: domains,
}
return doACMEForCSRWithDNS(t, dns, acmeClient, domains, cr)
}
func doACMEForCSRWithDNS(t *testing.T, dns *dnstest.TestServer, acmeClient *acme.Client, domains []string, cr *x509.CertificateRequest) *x509.Certificate {
accountKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed to generate account key")
acmeClient.Key = accountKey
testCtx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancelFunc()
// Register the client.
_, err = acmeClient.Register(testCtx, &acme.Account{Contact: []string{"mailto:ipsans@dadgarcorp.com"}}, func(tosURL string) bool { return true })
require.NoError(t, err, "failed registering account")
// Create the Order
var orderIdentifiers []acme.AuthzID
for _, domain := range domains {
orderIdentifiers = append(orderIdentifiers, acme.AuthzID{Type: "dns", Value: domain})
}
order, err := acmeClient.AuthorizeOrder(testCtx, orderIdentifiers)
require.NoError(t, err, "failed creating ACME order")
// Fetch its authorizations.
var auths []*acme.Authorization
for _, authUrl := range order.AuthzURLs {
authorization, err := acmeClient.GetAuthorization(testCtx, authUrl)
require.NoError(t, err, "failed to lookup authorization at url: %s", authUrl)
auths = append(auths, authorization)
}
// For each dns-01 challenge, place the record in the associated DNS resolver.
var challengesToAccept []*acme.Challenge
for _, auth := range auths {
for _, challenge := range auth.Challenges {
if challenge.Status != acme.StatusPending {
t.Logf("ignoring challenge not in status pending: %v", challenge)
continue
}
if challenge.Type == "dns-01" {
challengeBody, err := acmeClient.DNS01ChallengeRecord(challenge.Token)
require.NoError(t, err, "failed generating challenge response")
dns.AddRecord("_acme-challenge."+auth.Identifier.Value, "TXT", challengeBody)
defer dns.RemoveRecord("_acme-challenge."+auth.Identifier.Value, "TXT", challengeBody)
require.NoError(t, err, "failed setting DNS record")
challengesToAccept = append(challengesToAccept, challenge)
}
}
}
dns.PushConfig()
require.GreaterOrEqual(t, len(challengesToAccept), 1, "Need at least one challenge, got none")
// Tell the ACME server, that they can now validate those challenges.
for _, challenge := range challengesToAccept {
_, err = acmeClient.Accept(testCtx, challenge)
require.NoError(t, err, "failed to accept challenge: %v", challenge)
}
// Wait for the order/challenges to be validated.
_, err = acmeClient.WaitOrder(testCtx, order.URI)
require.NoError(t, err, "failed waiting for order to be ready")
// Create/sign the CSR and ask ACME server to sign it returning us the final certificate
csrKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
csr, err := x509.CreateCertificateRequest(rand.Reader, cr, csrKey)
require.NoError(t, err, "failed generating csr")
certs, _, err := acmeClient.CreateOrderCert(testCtx, order.FinalizeURL, csr, false)
require.NoError(t, err, "failed to get a certificate back from ACME")
acmeCert, err := x509.ParseCertificate(certs[0])
require.NoError(t, err, "failed parsing acme cert bytes")
return acmeCert
}

View File

@ -1,759 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/builtin/logical/pki/dnstest"
"github.com/stretchr/testify/require"
)
type keyAuthorizationTestCase struct {
keyAuthz string
token string
thumbprint string
shouldFail bool
}
var keyAuthorizationTestCases = []keyAuthorizationTestCase{
{
// Entirely empty
"",
"non-empty-token",
"non-empty-thumbprint",
true,
},
{
// Both empty
".",
"non-empty-token",
"non-empty-thumbprint",
true,
},
{
// Not equal
"non-.non-",
"non-empty-token",
"non-empty-thumbprint",
true,
},
{
// Empty thumbprint
"non-.",
"non-empty-token",
"non-empty-thumbprint",
true,
},
{
// Empty token
".non-",
"non-empty-token",
"non-empty-thumbprint",
true,
},
{
// Wrong order
"non-empty-thumbprint.non-empty-token",
"non-empty-token",
"non-empty-thumbprint",
true,
},
{
// Too many pieces
"one.two.three",
"non-empty-token",
"non-empty-thumbprint",
true,
},
{
// Valid
"non-empty-token.non-empty-thumbprint",
"non-empty-token",
"non-empty-thumbprint",
false,
},
}
func TestAcmeValidateKeyAuthorization(t *testing.T) {
t.Parallel()
for index, tc := range keyAuthorizationTestCases {
t.Run("subtest-"+strconv.Itoa(index), func(st *testing.T) {
isValid, err := ValidateKeyAuthorization(tc.keyAuthz, tc.token, tc.thumbprint)
if !isValid && err == nil {
st.Fatalf("[%d] expected failure to give reason via err (%v / %v)", index, isValid, err)
}
expectedValid := !tc.shouldFail
if expectedValid != isValid {
st.Fatalf("[%d] got ret=%v, expected ret=%v (shouldFail=%v)", index, isValid, expectedValid, tc.shouldFail)
}
})
}
}
func TestAcmeValidateHTTP01Challenge(t *testing.T) {
t.Parallel()
for index, tc := range keyAuthorizationTestCases {
validFunc := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(tc.keyAuthz))
}
withPadding := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(" " + tc.keyAuthz + " "))
}
withRedirect := func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/.well-known/") {
http.Redirect(w, r, "/my-http-01-challenge-response", 301)
return
}
w.Write([]byte(tc.keyAuthz))
}
withSleep := func(w http.ResponseWriter, r *http.Request) {
// Long enough to ensure any excessively short timeouts are hit,
// not long enough to trigger a failure (hopefully).
time.Sleep(5 * time.Second)
w.Write([]byte(tc.keyAuthz))
}
validHandlers := []http.HandlerFunc{
http.HandlerFunc(validFunc), http.HandlerFunc(withPadding),
http.HandlerFunc(withRedirect), http.HandlerFunc(withSleep),
}
for handlerIndex, handler := range validHandlers {
func() {
ts := httptest.NewServer(handler)
defer ts.Close()
host := ts.URL[7:]
isValid, err := ValidateHTTP01Challenge(host, tc.token, tc.thumbprint, &acmeConfigEntry{})
if !isValid && err == nil {
t.Fatalf("[tc=%d/handler=%d] expected failure to give reason via err (%v / %v)", index, handlerIndex, isValid, err)
}
expectedValid := !tc.shouldFail
if expectedValid != isValid {
t.Fatalf("[tc=%d/handler=%d] got ret=%v (err=%v), expected ret=%v (shouldFail=%v)", index, handlerIndex, isValid, err, expectedValid, tc.shouldFail)
}
}()
}
}
// Negative test cases for various HTTP-specific scenarios.
redirectLoop := func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/my-http-01-challenge-response", 301)
}
publicRedirect := func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "http://hashicorp.com/", 301)
}
noData := func(w http.ResponseWriter, r *http.Request) {}
noContent := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
notFound := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
simulateHang := func(w http.ResponseWriter, r *http.Request) {
time.Sleep(30 * time.Second)
w.Write([]byte("my-token.my-thumbprint"))
}
tooLarge := func(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 512; i++ {
w.Write([]byte("my-token.my-thumbprint\n"))
}
}
validHandlers := []http.HandlerFunc{
http.HandlerFunc(redirectLoop), http.HandlerFunc(publicRedirect),
http.HandlerFunc(noData), http.HandlerFunc(noContent),
http.HandlerFunc(notFound), http.HandlerFunc(simulateHang),
http.HandlerFunc(tooLarge),
}
for handlerIndex, handler := range validHandlers {
func() {
ts := httptest.NewServer(handler)
defer ts.Close()
host := ts.URL[7:]
isValid, err := ValidateHTTP01Challenge(host, "my-token", "my-thumbprint", &acmeConfigEntry{})
if isValid || err == nil {
t.Fatalf("[handler=%d] expected failure validating challenge (%v / %v)", handlerIndex, isValid, err)
}
}()
}
}
func TestAcmeValidateDNS01Challenge(t *testing.T) {
t.Parallel()
host := "dadgarcorp.com"
resolver := dnstest.SetupResolver(t, host)
defer resolver.Cleanup()
t.Logf("DNS Server Address: %v", resolver.GetLocalAddr())
config := &acmeConfigEntry{
DNSResolver: resolver.GetLocalAddr(),
}
for index, tc := range keyAuthorizationTestCases {
checksum := sha256.Sum256([]byte(tc.keyAuthz))
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
resolver.AddRecord(DNSChallengePrefix+host, "TXT", authz)
resolver.PushConfig()
isValid, err := ValidateDNS01Challenge(host, tc.token, tc.thumbprint, config)
if !isValid && err == nil {
t.Fatalf("[tc=%d] expected failure to give reason via err (%v / %v)", index, isValid, err)
}
expectedValid := !tc.shouldFail
if expectedValid != isValid {
t.Fatalf("[tc=%d] got ret=%v (err=%v), expected ret=%v (shouldFail=%v)", index, isValid, err, expectedValid, tc.shouldFail)
}
resolver.RemoveAllRecords()
}
}
func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
// This test is not parallel because we modify ALPNPort to use a custom
// non-standard port _just for testing purposes_.
host := "localhost"
config := &acmeConfigEntry{}
log := hclog.L()
returnedProtocols := []string{ALPNProtocol}
var certificates []*x509.Certificate
var privateKey crypto.PrivateKey
tlsCfg := &tls.Config{}
tlsCfg.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {
var retCfg tls.Config = *tlsCfg
retCfg.NextProtos = returnedProtocols
log.Info(fmt.Sprintf("[alpn-server] returned protocol: %v", returnedProtocols))
return &retCfg, nil
}
tlsCfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
var ret tls.Certificate
for index, cert := range certificates {
ret.Certificate = append(ret.Certificate, cert.Raw)
if index == 0 {
ret.Leaf = cert
}
}
ret.PrivateKey = privateKey
log.Info(fmt.Sprintf("[alpn-server] returned certificates: %v", ret))
return &ret, nil
}
ln, err := tls.Listen("tcp", host+":0", tlsCfg)
require.NoError(t, err, "failed to listen with TLS config")
doOneAccept := func() {
log.Info("[alpn-server] starting accept...")
connRaw, err := ln.Accept()
require.NoError(t, err, "failed to accept TLS connection")
log.Info("[alpn-server] got connection...")
conn := tls.Server(connRaw.(*tls.Conn), tlsCfg)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer func() {
log.Info("[alpn-server] canceling listener connection...")
cancel()
}()
log.Info("[alpn-server] starting handshake...")
if err := conn.HandshakeContext(ctx); err != nil {
log.Info("[alpn-server] got non-fatal error while handshaking connection: %v", err)
}
log.Info("[alpn-server] closing connection...")
if err := conn.Close(); err != nil {
log.Info("[alpn-server] got non-fatal error while closing connection: %v", err)
}
}
ALPNPort = strings.Split(ln.Addr().String(), ":")[1]
type alpnTestCase struct {
name string
certificates []*x509.Certificate
privateKey crypto.PrivateKey
protocols []string
token string
thumbprint string
shouldFail bool
}
var alpnTestCases []alpnTestCase
// Add all of our keyAuthorizationTestCases into alpnTestCases
for index, tc := range keyAuthorizationTestCases {
log.Info(fmt.Sprintf("using keyAuthorizationTestCase [tc=%d] as alpnTestCase [tc=%d]...", index, len(alpnTestCases)))
// Properly encode the authorization.
checksum := sha256.Sum256([]byte(tc.keyAuthz))
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed asn.1 marshalling authz")
// Build a self-signed certificate.
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{
CommonName: host,
},
Issuer: pkix.Name{
CommonName: host,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: key.Public(),
SerialNumber: big.NewInt(1),
DNSNames: []string{host},
ExtraExtensions: []pkix.Extension{
{
Id: OIDACMEIdentifier,
Critical: true,
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create certificate")
cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err, "failed to parse newly generated certificate")
newTc := alpnTestCase{
name: fmt.Sprintf("keyAuthorizationTestCase[%d]", index),
certificates: []*x509.Certificate{cert},
privateKey: key,
protocols: []string{ALPNProtocol},
token: tc.token,
thumbprint: tc.thumbprint,
shouldFail: tc.shouldFail,
}
alpnTestCases = append(alpnTestCases, newTc)
}
{
// Test case: Longer chain
// Build a self-signed certificate.
rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating root private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{
CommonName: "Root CA",
},
Issuer: pkix.Name{
CommonName: "Root CA",
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: rootKey.Public(),
SerialNumber: big.NewInt(1),
BasicConstraintsValid: true,
IsCA: true,
}
rootCertBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, rootKey.Public(), rootKey)
require.NoError(t, err, "failed to create root certificate")
rootCert, err := x509.ParseCertificate(rootCertBytes)
require.NoError(t, err, "failed to parse newly generated root certificate")
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")
// Build a leaf certificate which _could_ pass validation
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating leaf private key")
tmpl = &x509.Certificate{
Subject: pkix.Name{
CommonName: host,
},
Issuer: pkix.Name{
CommonName: "Root CA",
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: key.Public(),
SerialNumber: big.NewInt(2),
DNSNames: []string{host},
ExtraExtensions: []pkix.Extension{
{
Id: OIDACMEIdentifier,
Critical: true,
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, rootCert, key.Public(), rootKey)
require.NoError(t, err, "failed to create leaf certificate")
cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err, "failed to parse newly generated leaf certificate")
newTc := alpnTestCase{
name: "longer chain with valid leaf",
certificates: []*x509.Certificate{cert, rootCert},
privateKey: key,
protocols: []string{ALPNProtocol},
token: "valid",
thumbprint: "valid",
shouldFail: true,
}
alpnTestCases = append(alpnTestCases, newTc)
}
{
// Test case: cert without DNSSan
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")
// Build a leaf certificate without a DNSSan
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating leaf private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{
CommonName: host,
},
Issuer: pkix.Name{
CommonName: host,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: key.Public(),
SerialNumber: big.NewInt(2),
// NO DNSNames
ExtraExtensions: []pkix.Extension{
{
Id: OIDACMEIdentifier,
Critical: true,
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err, "failed to parse newly generated leaf certificate")
newTc := alpnTestCase{
name: "valid keyauthz without valid dnsname",
certificates: []*x509.Certificate{cert},
privateKey: key,
protocols: []string{ALPNProtocol},
token: "valid",
thumbprint: "valid",
shouldFail: true,
}
alpnTestCases = append(alpnTestCases, newTc)
}
{
// Test case: cert without matching DNSSan
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")
// Build a leaf certificate which fails validation due to bad DNSName
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating leaf private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{
CommonName: host,
},
Issuer: pkix.Name{
CommonName: host,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: key.Public(),
SerialNumber: big.NewInt(2),
DNSNames: []string{host + ".dadgarcorp.com" /* not matching host! */},
ExtraExtensions: []pkix.Extension{
{
Id: OIDACMEIdentifier,
Critical: true,
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err, "failed to parse newly generated leaf certificate")
newTc := alpnTestCase{
name: "valid keyauthz without matching dnsname",
certificates: []*x509.Certificate{cert},
privateKey: key,
protocols: []string{ALPNProtocol},
token: "valid",
thumbprint: "valid",
shouldFail: true,
}
alpnTestCases = append(alpnTestCases, newTc)
}
{
// Test case: cert with additional SAN
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")
// Build a leaf certificate which has an invalid additional SAN
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating leaf private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{
CommonName: host,
},
Issuer: pkix.Name{
CommonName: host,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: key.Public(),
SerialNumber: big.NewInt(2),
DNSNames: []string{host},
EmailAddresses: []string{"webmaster@" + host}, /* unexpected */
ExtraExtensions: []pkix.Extension{
{
Id: OIDACMEIdentifier,
Critical: true,
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err, "failed to parse newly generated leaf certificate")
newTc := alpnTestCase{
name: "valid keyauthz with additional email SANs",
certificates: []*x509.Certificate{cert},
privateKey: key,
protocols: []string{ALPNProtocol},
token: "valid",
thumbprint: "valid",
shouldFail: true,
}
alpnTestCases = append(alpnTestCases, newTc)
}
{
// Test case: cert without CN
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")
// Build a leaf certificate which should pass validation
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating leaf private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{},
Issuer: pkix.Name{},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: key.Public(),
SerialNumber: big.NewInt(2),
DNSNames: []string{host},
ExtraExtensions: []pkix.Extension{
{
Id: OIDACMEIdentifier,
Critical: true,
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err, "failed to parse newly generated leaf certificate")
newTc := alpnTestCase{
name: "valid certificate; no Subject/Issuer (missing CN)",
certificates: []*x509.Certificate{cert},
privateKey: key,
protocols: []string{ALPNProtocol},
token: "valid",
thumbprint: "valid",
shouldFail: false,
}
alpnTestCases = append(alpnTestCases, newTc)
}
{
// Test case: cert without the extension
// Build a leaf certificate which should fail validation
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating leaf private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{},
Issuer: pkix.Name{},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: key.Public(),
SerialNumber: big.NewInt(1),
DNSNames: []string{host},
BasicConstraintsValid: true,
IsCA: true,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
cert, err := x509.ParseCertificate(certBytes)
require.NoError(t, err, "failed to parse newly generated leaf certificate")
newTc := alpnTestCase{
name: "missing required acmeIdentifier extension",
certificates: []*x509.Certificate{cert},
privateKey: key,
protocols: []string{ALPNProtocol},
token: "valid",
thumbprint: "valid",
shouldFail: true,
}
alpnTestCases = append(alpnTestCases, newTc)
}
{
// Test case: root without a leaf
// Build a self-signed certificate.
rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err, "failed generating root private key")
tmpl := &x509.Certificate{
Subject: pkix.Name{
CommonName: "Root CA",
},
Issuer: pkix.Name{
CommonName: "Root CA",
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
PublicKey: rootKey.Public(),
SerialNumber: big.NewInt(1),
BasicConstraintsValid: true,
IsCA: true,
}
rootCertBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, rootKey.Public(), rootKey)
require.NoError(t, err, "failed to create root certificate")
rootCert, err := x509.ParseCertificate(rootCertBytes)
require.NoError(t, err, "failed to parse newly generated root certificate")
newTc := alpnTestCase{
name: "root without leaf",
certificates: []*x509.Certificate{rootCert},
privateKey: rootKey,
protocols: []string{ALPNProtocol},
token: "valid",
thumbprint: "valid",
shouldFail: true,
}
alpnTestCases = append(alpnTestCases, newTc)
}
for index, tc := range alpnTestCases {
log.Info(fmt.Sprintf("\n\n[tc=%d/name=%s] starting validation", index, tc.name))
certificates = tc.certificates
privateKey = tc.privateKey
returnedProtocols = tc.protocols
// Attempt to validate the challenge.
go doOneAccept()
isValid, err := ValidateTLSALPN01Challenge(host, tc.token, tc.thumbprint, config)
if !isValid && err == nil {
t.Fatalf("[tc=%d/name=%s] expected failure to give reason via err (%v / %v)", index, tc.name, isValid, err)
}
expectedValid := !tc.shouldFail
if expectedValid != isValid {
t.Fatalf("[tc=%d/name=%s] got ret=%v (err=%v), expected ret=%v (shouldFail=%v)", index, tc.name, isValid, err, expectedValid, tc.shouldFail)
} else if err != nil {
log.Info(fmt.Sprintf("[tc=%d/name=%s] got expected failure: err=%v", index, tc.name, err))
}
}
}
// TestAcmeValidateHttp01TLSRedirect verify that we allow a http-01 challenge to redirect
// to a TLS server and not validate the certificate chain is valid. We don't validate the
// TLS chain as we would have accepted the auth over a non-secured channel anyway had
// the original request not redirected us.
func TestAcmeValidateHttp01TLSRedirect(t *testing.T) {
t.Parallel()
for index, tc := range keyAuthorizationTestCases {
t.Run("subtest-"+strconv.Itoa(index), func(st *testing.T) {
validFunc := func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/.well-known/") {
w.Write([]byte(tc.keyAuthz))
return
}
http.Error(w, "status not found", http.StatusNotFound)
}
tlsTs := httptest.NewTLSServer(http.HandlerFunc(validFunc))
defer tlsTs.Close()
// Set up a http server that will redirect to our TLS server
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, tlsTs.URL+r.URL.Path, 301)
}))
defer ts.Close()
host := ts.URL[len("http://"):]
isValid, err := ValidateHTTP01Challenge(host, tc.token, tc.thumbprint, &acmeConfigEntry{})
if !isValid && err == nil {
st.Fatalf("[tc=%d] expected failure to give reason via err (%v / %v)", index, isValid, err)
}
expectedValid := !tc.shouldFail
if expectedValid != isValid {
st.Fatalf("[tc=%d] got ret=%v (err=%v), expected ret=%v (shouldFail=%v)", index, isValid, err, expectedValid, tc.shouldFail)
}
})
}
}

View File

@ -1,43 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAcmeNonces(t *testing.T) {
t.Parallel()
a := NewACMEState()
a.nonces.Initialize()
// Simple operation should succeed.
nonce, _, err := a.GetNonce()
require.NoError(t, err)
require.NotEmpty(t, nonce)
require.True(t, a.RedeemNonce(nonce))
require.False(t, a.RedeemNonce(nonce))
// Redeeming in opposite order should work.
var nonces []string
for i := 0; i < len(nonce); i++ {
nonce, _, err = a.GetNonce()
require.NoError(t, err)
require.NotEmpty(t, nonce)
}
for i := len(nonces) - 1; i >= 0; i-- {
nonce = nonces[i]
require.True(t, a.RedeemNonce(nonce))
}
for i := 0; i < len(nonces); i++ {
nonce = nonces[i]
require.False(t, a.RedeemNonce(nonce))
}
}

View File

@ -1,125 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"fmt"
"strings"
"testing"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
// TestACMEIssuerRoleLoading validates the role and issuer loading logic within the base
// ACME wrapper is correct.
func TestACMEIssuerRoleLoading(t *testing.T) {
b, s := CreateBackendWithStorage(t)
_, err := CBWrite(b, s, "config/cluster", map[string]interface{}{
"path": "http://localhost:8200/v1/pki",
"aia_path": "http://localhost:8200/cdn/pki",
})
require.NoError(t, err)
_, err = CBWrite(b, s, "config/acme", map[string]interface{}{
"enabled": true,
})
require.NoError(t, err)
_, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "myvault1.com",
"issuer_name": "issuer-1",
"key_type": "ec",
})
require.NoError(t, err, "failed creating issuer issuer-1")
_, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "myvault2.com",
"issuer_name": "issuer-2",
"key_type": "ec",
})
require.NoError(t, err, "failed creating issuer issuer-2")
_, err = CBWrite(b, s, "roles/role-bad-issuer", map[string]interface{}{
issuerRefParam: "non-existant",
"no_store": "false",
})
require.NoError(t, err, "failed creating role role-bad-issuer")
_, err = CBWrite(b, s, "roles/role-no-store-enabled", map[string]interface{}{
issuerRefParam: "issuer-2",
"no_store": "true",
})
require.NoError(t, err, "failed creating role role-no-store-enabled")
_, err = CBWrite(b, s, "roles/role-issuer-2", map[string]interface{}{
issuerRefParam: "issuer-2",
"no_store": "false",
})
require.NoError(t, err, "failed creating role role-issuer-2")
tc := []struct {
name string
roleName string
issuerName string
expectedIssuerName string
expectErr bool
}{
{name: "pass-default-use-default", roleName: "", issuerName: "", expectedIssuerName: "issuer-1", expectErr: false},
{name: "pass-role-issuer-2", roleName: "role-issuer-2", issuerName: "", expectedIssuerName: "issuer-2", expectErr: false},
{name: "pass-issuer-1-no-role", roleName: "", issuerName: "issuer-1", expectedIssuerName: "issuer-1", expectErr: false},
{name: "fail-role-has-bad-issuer", roleName: "role-bad-issuer", issuerName: "", expectedIssuerName: "", expectErr: true},
{name: "fail-role-no-store-enabled", roleName: "role-no-store-enabled", issuerName: "", expectedIssuerName: "", expectErr: true},
{name: "fail-role-no-store-enabled", roleName: "role-no-store-enabled", issuerName: "", expectedIssuerName: "", expectErr: true},
{name: "fail-role-does-not-exist", roleName: "non-existant", issuerName: "", expectedIssuerName: "", expectErr: true},
{name: "fail-issuer-does-not-exist", roleName: "", issuerName: "non-existant", expectedIssuerName: "", expectErr: true},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
f := b.acmeWrapper(func(acmeCtx *acmeContext, r *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
if tt.roleName != acmeCtx.role.Name {
return nil, fmt.Errorf("expected role %s but got %s", tt.roleName, acmeCtx.role.Name)
}
if tt.expectedIssuerName != acmeCtx.issuer.Name {
return nil, fmt.Errorf("expected issuer %s but got %s", tt.expectedIssuerName, acmeCtx.issuer.Name)
}
return nil, nil
})
var acmePath string
fieldRaw := map[string]interface{}{}
if tt.issuerName != "" {
fieldRaw[issuerRefParam] = tt.issuerName
acmePath = "issuer/" + tt.issuerName + "/"
}
if tt.roleName != "" {
fieldRaw["role"] = tt.roleName
acmePath = acmePath + "roles/" + tt.roleName + "/"
}
acmePath = strings.TrimLeft(acmePath+"/acme/directory", "/")
resp, err := f(context.Background(), &logical.Request{Path: acmePath, Storage: s}, &framework.FieldData{
Raw: fieldRaw,
Schema: getCsrSignVerbatimSchemaFields(),
})
require.NoError(t, err, "all errors should be re-encoded")
if tt.expectErr {
require.NotEqual(t, 200, resp.Data[logical.HTTPStatusCode])
require.Equal(t, ErrorContentType, resp.Data[logical.HTTPContentType])
} else {
if resp != nil {
t.Fatalf("expected no error got %s", string(resp.Data[logical.HTTPRawBody].([]uint8)))
}
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,712 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"math/big"
mathrand "math/rand"
"strings"
"testing"
"time"
"github.com/go-test/deep"
"github.com/hashicorp/vault/api"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)
func TestBackend_CA_Steps(t *testing.T) {
t.Parallel()
var b *backend
factory := func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
be, err := Factory(ctx, conf)
if err == nil {
b = be.(*backend)
}
return be, err
}
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
// Set RSA/EC CA certificates
var rsaCAKey, rsaCACert, ecCAKey, ecCACert, edCAKey, edCACert string
{
cak, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
marshaledKey, err := x509.MarshalECPrivateKey(cak)
if err != nil {
panic(err)
}
keyPEMBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: marshaledKey,
}
ecCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
if err != nil {
panic(err)
}
subjKeyID, err := certutil.GetSubjKeyID(cak)
if err != nil {
panic(err)
}
caCertTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "root.localhost",
},
SubjectKeyId: subjKeyID,
DNSNames: []string{"root.localhost"},
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
SerialNumber: big.NewInt(mathrand.Int63()),
NotAfter: time.Now().Add(262980 * time.Hour),
BasicConstraintsValid: true,
IsCA: true,
}
caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, cak.Public(), cak)
if err != nil {
panic(err)
}
caCertPEMBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
}
ecCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
rak, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
marshaledKey = x509.MarshalPKCS1PrivateKey(rak)
keyPEMBlock = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: marshaledKey,
}
rsaCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
if err != nil {
panic(err)
}
_, err = certutil.GetSubjKeyID(rak)
if err != nil {
panic(err)
}
caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, rak.Public(), rak)
if err != nil {
panic(err)
}
caCertPEMBlock = &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
}
rsaCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
_, edk, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
marshaledKey, err = x509.MarshalPKCS8PrivateKey(edk)
if err != nil {
panic(err)
}
keyPEMBlock = &pem.Block{
Type: "PRIVATE KEY",
Bytes: marshaledKey,
}
edCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
if err != nil {
panic(err)
}
_, err = certutil.GetSubjKeyID(edk)
if err != nil {
panic(err)
}
caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, edk.Public(), edk)
if err != nil {
panic(err)
}
caCertPEMBlock = &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
}
edCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
}
// Setup backends
var rsaRoot, rsaInt, ecRoot, ecInt, edRoot, edInt *backend
{
if err := client.Sys().Mount("rsaroot", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
}); err != nil {
t.Fatal(err)
}
rsaRoot = b
if err := client.Sys().Mount("rsaint", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
}); err != nil {
t.Fatal(err)
}
rsaInt = b
if err := client.Sys().Mount("ecroot", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
}); err != nil {
t.Fatal(err)
}
ecRoot = b
if err := client.Sys().Mount("ecint", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
}); err != nil {
t.Fatal(err)
}
ecInt = b
if err := client.Sys().Mount("ed25519root", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
}); err != nil {
t.Fatal(err)
}
edRoot = b
if err := client.Sys().Mount("ed25519int", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
}); err != nil {
t.Fatal(err)
}
edInt = b
}
t.Run("teststeps", func(t *testing.T) {
t.Run("rsa", func(t *testing.T) {
t.Parallel()
subClient, err := client.Clone()
if err != nil {
t.Fatal(err)
}
subClient.SetToken(client.Token())
runSteps(t, rsaRoot, rsaInt, subClient, "rsaroot/", "rsaint/", rsaCACert, rsaCAKey)
})
t.Run("ec", func(t *testing.T) {
t.Parallel()
subClient, err := client.Clone()
if err != nil {
t.Fatal(err)
}
subClient.SetToken(client.Token())
runSteps(t, ecRoot, ecInt, subClient, "ecroot/", "ecint/", ecCACert, ecCAKey)
})
t.Run("ed25519", func(t *testing.T) {
t.Parallel()
subClient, err := client.Clone()
if err != nil {
t.Fatal(err)
}
subClient.SetToken(client.Token())
runSteps(t, edRoot, edInt, subClient, "ed25519root/", "ed25519int/", edCACert, edCAKey)
})
})
}
func runSteps(t *testing.T, rootB, intB *backend, client *api.Client, rootName, intName, caCert, caKey string) {
// Load CA cert/key in and ensure we can fetch it back in various formats,
// unauthenticated
{
// Attempt import but only provide one the cert; this should work.
{
_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
"pem_bundle": caCert,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
// Same but with only the key
{
_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
"pem_bundle": caKey,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
// Import entire CA bundle; this should work as well
{
_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
"pem_bundle": strings.Join([]string{caKey, caCert}, "\n"),
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
prevToken := client.Token()
client.SetToken("")
// cert/ca and issuer/default/json path
for _, path := range []string{"cert/ca", "issuer/default/json"} {
resp, err := client.Logical().Read(rootName + path)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
expected := caCert
if path == "issuer/default/json" {
// Preserves the new line.
expected += "\n"
_, present := resp.Data["issuer_id"]
if !present {
t.Fatalf("expected issuer/default/json to include issuer_id")
}
_, present = resp.Data["issuer_name"]
if !present {
t.Fatalf("expected issuer/default/json to include issuer_name")
}
}
if diff := deep.Equal(resp.Data["certificate"].(string), expected); diff != nil {
t.Fatal(diff)
}
}
// ca/pem and issuer/default/pem path (raw string)
for _, path := range []string{"ca/pem", "issuer/default/pem"} {
req := &logical.Request{
Path: path,
Operation: logical.ReadOperation,
Storage: rootB.storage,
}
resp, err := rootB.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
expected := []byte(caCert)
if path == "issuer/default/pem" {
// Preserves the new line.
expected = []byte(caCert + "\n")
}
if diff := deep.Equal(resp.Data["http_raw_body"].([]byte), expected); diff != nil {
t.Fatal(diff)
}
if resp.Data["http_content_type"].(string) != "application/pem-certificate-chain" {
t.Fatal("wrong content type")
}
}
// ca and issuer/default/der (raw DER bytes)
for _, path := range []string{"ca", "issuer/default/der"} {
req := &logical.Request{
Path: path,
Operation: logical.ReadOperation,
Storage: rootB.storage,
}
resp, err := rootB.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
rawBytes := resp.Data["http_raw_body"].([]byte)
pemBytes := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: rawBytes,
})))
if diff := deep.Equal(pemBytes, caCert); diff != nil {
t.Fatal(diff)
}
if resp.Data["http_content_type"].(string) != "application/pkix-cert" {
t.Fatal("wrong content type")
}
}
client.SetToken(prevToken)
}
// Configure an expiry on the CRL and verify what comes back
{
// Set CRL config
{
_, err := client.Logical().Write(rootName+"config/crl", map[string]interface{}{
"expiry": "16h",
})
if err != nil {
t.Fatal(err)
}
}
// Verify it
{
resp, err := client.Logical().Read(rootName + "config/crl")
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
if resp.Data["expiry"].(string) != "16h" {
t.Fatal("expected a 16 hour expiry")
}
}
}
// Test generating a root, an intermediate, signing it, setting signed, and
// revoking it
// We'll need this later
var intSerialNumber string
{
// First, delete the existing CA info
{
_, err := client.Logical().Delete(rootName + "root")
if err != nil {
t.Fatal(err)
}
}
var rootPEM, rootKey, rootPEMBundle string
// Test exported root generation
{
resp, err := client.Logical().Write(rootName+"root/generate/exported", map[string]interface{}{
"common_name": "Root Cert",
"ttl": "180h",
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
rootPEM = resp.Data["certificate"].(string)
rootKey = resp.Data["private_key"].(string)
rootPEMBundle = strings.Join([]string{rootPEM, rootKey}, "\n")
// This is really here to keep the use checker happy
if rootPEMBundle == "" {
t.Fatal("bad root pem bundle")
}
}
var intPEM, intCSR, intKey string
// Test exported intermediate CSR generation
{
resp, err := client.Logical().Write(intName+"intermediate/generate/exported", map[string]interface{}{
"common_name": "intermediate.cert.com",
"ttl": "180h",
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
intCSR = resp.Data["csr"].(string)
intKey = resp.Data["private_key"].(string)
// This is really here to keep the use checker happy
if intCSR == "" || intKey == "" {
t.Fatal("int csr or key empty")
}
}
// Test signing
{
resp, err := client.Logical().Write(rootName+"root/sign-intermediate", map[string]interface{}{
"common_name": "intermediate.cert.com",
"ttl": "10s",
"csr": intCSR,
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
intPEM = resp.Data["certificate"].(string)
intSerialNumber = resp.Data["serial_number"].(string)
}
// Test setting signed
{
resp, err := client.Logical().Write(intName+"intermediate/set-signed", map[string]interface{}{
"certificate": intPEM,
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
}
// Verify we can find it via the root
{
resp, err := client.Logical().Read(rootName + "cert/" + intSerialNumber)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
if resp.Data["revocation_time"].(json.Number).String() != "0" {
t.Fatal("expected a zero revocation time")
}
}
// Revoke the intermediate
{
resp, err := client.Logical().Write(rootName+"revoke", map[string]interface{}{
"serial_number": intSerialNumber,
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
}
}
verifyRevocation := func(t *testing.T, serial string, shouldFind bool) {
t.Helper()
// Verify it is now revoked
{
resp, err := client.Logical().Read(rootName + "cert/" + intSerialNumber)
if err != nil {
t.Fatal(err)
}
switch shouldFind {
case true:
if resp == nil {
t.Fatal("nil response")
}
if resp.Data["revocation_time"].(json.Number).String() == "0" {
t.Fatal("expected a non-zero revocation time")
}
default:
if resp != nil {
t.Fatalf("expected nil response, got %#v", *resp)
}
}
}
// Fetch the CRL and make sure it shows up
for path, derPemOrJSON := range map[string]int{
"crl": 0,
"issuer/default/crl/der": 0,
"crl/pem": 1,
"issuer/default/crl/pem": 1,
"cert/crl": 2,
"issuer/default/crl": 3,
} {
req := &logical.Request{
Path: path,
Operation: logical.ReadOperation,
Storage: rootB.storage,
}
resp, err := rootB.HandleRequest(context.Background(), req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
var crlBytes []byte
if derPemOrJSON == 2 {
// Old endpoint
crlBytes = []byte(resp.Data["certificate"].(string))
} else if derPemOrJSON == 3 {
// New endpoint
crlBytes = []byte(resp.Data["crl"].(string))
} else {
// DER or PEM
crlBytes = resp.Data["http_raw_body"].([]byte)
}
if derPemOrJSON >= 1 {
// Do for both PEM and JSON endpoints
pemBlock, _ := pem.Decode(crlBytes)
crlBytes = pemBlock.Bytes
}
certList, err := x509.ParseCRL(crlBytes)
if err != nil {
t.Fatal(err)
}
switch shouldFind {
case true:
revokedList := certList.TBSCertList.RevokedCertificates
if len(revokedList) != 1 {
t.Fatalf("bad length of revoked list: %d", len(revokedList))
}
revokedString := certutil.GetHexFormatted(revokedList[0].SerialNumber.Bytes(), ":")
if revokedString != intSerialNumber {
t.Fatalf("bad revoked serial: %s", revokedString)
}
default:
revokedList := certList.TBSCertList.RevokedCertificates
if len(revokedList) != 0 {
t.Fatalf("bad length of revoked list: %d", len(revokedList))
}
}
}
}
verifyTidyStatus := func(expectedCertStoreDeleteCount int, expectedRevokedCertDeletedCount int) {
tidyStatus, err := client.Logical().Read(rootName + "tidy-status")
if err != nil {
t.Fatal(err)
}
if tidyStatus.Data["state"] != "Finished" {
t.Fatalf("Expected tidy operation to be finished, but tidy-status reports its state is %v", tidyStatus.Data)
}
var count int64
if count, err = tidyStatus.Data["cert_store_deleted_count"].(json.Number).Int64(); err != nil {
t.Fatal(err)
}
if int64(expectedCertStoreDeleteCount) != count {
t.Fatalf("Expected %d for cert_store_deleted_count, but got %d", expectedCertStoreDeleteCount, count)
}
if count, err = tidyStatus.Data["revoked_cert_deleted_count"].(json.Number).Int64(); err != nil {
t.Fatal(err)
}
if int64(expectedRevokedCertDeletedCount) != count {
t.Fatalf("Expected %d for revoked_cert_deleted_count, but got %d", expectedRevokedCertDeletedCount, count)
}
}
// Validate current state of revoked certificates
verifyRevocation(t, intSerialNumber, true)
// Give time for the safety buffer to pass before tidying
time.Sleep(10 * time.Second)
// Test tidying
{
// Run with a high safety buffer, nothing should happen
{
resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
"safety_buffer": "3h",
"tidy_cert_store": true,
"tidy_revoked_certs": true,
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected warnings")
}
// Wait a few seconds as it runs in a goroutine
time.Sleep(5 * time.Second)
// Check to make sure we still find the cert and see it on the CRL
verifyRevocation(t, intSerialNumber, true)
verifyTidyStatus(0, 0)
}
// Run with both values set false, nothing should happen
{
resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
"safety_buffer": "1s",
"tidy_cert_store": false,
"tidy_revoked_certs": false,
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected warnings")
}
// Wait a few seconds as it runs in a goroutine
time.Sleep(5 * time.Second)
// Check to make sure we still find the cert and see it on the CRL
verifyRevocation(t, intSerialNumber, true)
verifyTidyStatus(0, 0)
}
// Run with a short safety buffer and both set to true, both should be cleared
{
resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
"safety_buffer": "1s",
"tidy_cert_store": true,
"tidy_revoked_certs": true,
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected warnings")
}
// Wait a few seconds as it runs in a goroutine
time.Sleep(5 * time.Second)
// Check to make sure we still find the cert and see it on the CRL
verifyRevocation(t, intSerialNumber, false)
verifyTidyStatus(1, 1)
}
}
}

View File

@ -1,237 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"fmt"
"reflect"
"strings"
"testing"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func TestPki_FetchCertBySerial(t *testing.T) {
t.Parallel()
b, storage := CreateBackendWithStorage(t)
sc := b.makeStorageContext(ctx, storage)
cases := map[string]struct {
Req *logical.Request
Prefix string
Serial string
}{
"valid cert": {
&logical.Request{
Storage: storage,
},
"certs/",
"00:00:00:00:00:00:00:00",
},
"revoked cert": {
&logical.Request{
Storage: storage,
},
"revoked/",
"11:11:11:11:11:11:11:11",
},
}
// Test for colon-based paths in storage
for name, tc := range cases {
storageKey := fmt.Sprintf("%s%s", tc.Prefix, tc.Serial)
err := storage.Put(context.Background(), &logical.StorageEntry{
Key: storageKey,
Value: []byte("some data"),
})
if err != nil {
t.Fatalf("error writing to storage on %s colon-based storage path: %s", name, err)
}
certEntry, err := fetchCertBySerial(sc, tc.Prefix, tc.Serial)
if err != nil {
t.Fatalf("error on %s for colon-based storage path: %s", name, err)
}
// Check for non-nil on valid/revoked certs
if certEntry == nil {
t.Fatalf("nil on %s for colon-based storage path", name)
}
// Ensure that cert serials are converted/updated after fetch
expectedKey := tc.Prefix + normalizeSerial(tc.Serial)
se, err := storage.Get(context.Background(), expectedKey)
if err != nil {
t.Fatalf("error on %s for colon-based storage path:%s", name, err)
}
if strings.Compare(expectedKey, se.Key) != 0 {
t.Fatalf("expected: %s, got: %s", expectedKey, certEntry.Key)
}
}
// Reset storage
storage = &logical.InmemStorage{}
// Test for hyphen-base paths in storage
for name, tc := range cases {
storageKey := tc.Prefix + normalizeSerial(tc.Serial)
err := storage.Put(context.Background(), &logical.StorageEntry{
Key: storageKey,
Value: []byte("some data"),
})
if err != nil {
t.Fatalf("error writing to storage on %s hyphen-based storage path: %s", name, err)
}
certEntry, err := fetchCertBySerial(sc, tc.Prefix, tc.Serial)
if err != nil || certEntry == nil {
t.Fatalf("error on %s for hyphen-based storage path: err: %v, entry: %v", name, err, certEntry)
}
}
}
// Demonstrate that multiple OUs in the name are handled in an
// order-preserving way.
func TestPki_MultipleOUs(t *testing.T) {
t.Parallel()
var b backend
fields := addCACommonFields(map[string]*framework.FieldSchema{})
apiData := &framework.FieldData{
Schema: fields,
Raw: map[string]interface{}{
"cn": "example.com",
"ttl": 3600,
},
}
input := &inputBundle{
apiData: apiData,
role: &roleEntry{
MaxTTL: 3600,
OU: []string{"Z", "E", "V"},
},
}
cb, _, err := generateCreationBundle(&b, input, nil, nil)
if err != nil {
t.Fatalf("Error: %v", err)
}
expected := []string{"Z", "E", "V"}
actual := cb.Params.Subject.OrganizationalUnit
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("Expected %v, got %v", expected, actual)
}
}
func TestPki_PermitFQDNs(t *testing.T) {
t.Parallel()
var b backend
fields := addCACommonFields(map[string]*framework.FieldSchema{})
cases := map[string]struct {
input *inputBundle
expectedDnsNames []string
expectedEmails []string
}{
"base valid case": {
input: &inputBundle{
apiData: &framework.FieldData{
Schema: fields,
Raw: map[string]interface{}{
"common_name": "example.com.",
"ttl": 3600,
},
},
role: &roleEntry{
AllowAnyName: true,
MaxTTL: 3600,
EnforceHostnames: true,
},
},
expectedDnsNames: []string{"example.com."},
expectedEmails: []string{},
},
"case insensitivity validation": {
input: &inputBundle{
apiData: &framework.FieldData{
Schema: fields,
Raw: map[string]interface{}{
"common_name": "Example.Net",
"alt_names": "eXaMPLe.COM",
"ttl": 3600,
},
},
role: &roleEntry{
AllowedDomains: []string{"example.net", "EXAMPLE.COM"},
AllowBareDomains: true,
MaxTTL: 3600,
},
},
expectedDnsNames: []string{"Example.Net", "eXaMPLe.COM"},
expectedEmails: []string{},
},
"case email as AllowedDomain with bare domains": {
input: &inputBundle{
apiData: &framework.FieldData{
Schema: fields,
Raw: map[string]interface{}{
"common_name": "test@testemail.com",
"ttl": 3600,
},
},
role: &roleEntry{
AllowedDomains: []string{"test@testemail.com"},
AllowBareDomains: true,
MaxTTL: 3600,
},
},
expectedDnsNames: []string{},
expectedEmails: []string{"test@testemail.com"},
},
"case email common name with bare domains": {
input: &inputBundle{
apiData: &framework.FieldData{
Schema: fields,
Raw: map[string]interface{}{
"common_name": "test@testemail.com",
"ttl": 3600,
},
},
role: &roleEntry{
AllowedDomains: []string{"testemail.com"},
AllowBareDomains: true,
MaxTTL: 3600,
},
},
expectedDnsNames: []string{},
expectedEmails: []string{"test@testemail.com"},
},
}
for name, testCase := range cases {
name := name
testCase := testCase
t.Run(name, func(t *testing.T) {
cb, _, err := generateCreationBundle(&b, testCase.input, nil, nil)
if err != nil {
t.Fatalf("Error: %v", err)
}
actualDnsNames := cb.Params.DNSNames
if !reflect.DeepEqual(testCase.expectedDnsNames, actualDnsNames) {
t.Fatalf("Expected dns names %v, got %v", testCase.expectedDnsNames, actualDnsNames)
}
actualEmails := cb.Params.EmailAddresses
if !reflect.DeepEqual(testCase.expectedEmails, actualEmails) {
t.Fatalf("Expected email addresses %v, got %v", testCase.expectedEmails, actualEmails)
}
})
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,428 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package dnstest
import (
"context"
"fmt"
"net"
"strings"
"sync"
"testing"
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/hashicorp/vault/sdk/helper/docker"
"github.com/stretchr/testify/require"
)
type TestServer struct {
t *testing.T
ctx context.Context
log hclog.Logger
runner *docker.Runner
network string
startup *docker.Service
lock sync.Mutex
serial int
forwarders []string
domains []string
records map[string]map[string][]string // domain -> record -> value(s).
cleanup func()
}
func SetupResolver(t *testing.T, domain string) *TestServer {
return SetupResolverOnNetwork(t, domain, "")
}
func SetupResolverOnNetwork(t *testing.T, domain string, network string) *TestServer {
var ts TestServer
ts.t = t
ts.ctx = context.Background()
ts.domains = []string{domain}
ts.records = map[string]map[string][]string{}
ts.network = network
ts.log = hclog.L()
ts.setupRunner(domain, network)
ts.startContainer(network)
ts.PushConfig()
return &ts
}
func (ts *TestServer) setupRunner(domain string, network string) {
var err error
ts.runner, err = docker.NewServiceRunner(docker.RunOptions{
ImageRepo: "ubuntu/bind9",
ImageTag: "latest",
ContainerName: "bind9-dns-" + strings.ReplaceAll(domain, ".", "-"),
NetworkName: network,
Ports: []string{"53/udp"},
// DNS container logging was disabled to reduce content within CI logs.
//LogConsumer: func(s string) {
// ts.log.Info(s)
//},
})
require.NoError(ts.t, err)
}
func (ts *TestServer) startContainer(network string) {
connUpFunc := func(ctx context.Context, host string, port int) (docker.ServiceConfig, error) {
// Perform a simple connection to this resolver, even though the
// default configuration doesn't do anything useful.
peer, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return nil, fmt.Errorf("failed to resolve peer: %v / %v: %w", host, port, err)
}
conn, err := net.DialUDP("udp", nil, peer)
if err != nil {
return nil, fmt.Errorf("failed to dial peer: %v / %v / %v: %w", host, port, peer, err)
}
defer conn.Close()
_, err = conn.Write([]byte("garbage-in"))
if err != nil {
return nil, fmt.Errorf("failed to write to peer: %v / %v / %v: %w", host, port, peer, err)
}
// Connection worked.
return docker.NewServiceHostPort(host, port), nil
}
result, _, err := ts.runner.StartNewService(ts.ctx, true, true, connUpFunc)
require.NoError(ts.t, err, "failed to start dns resolver for "+ts.domains[0])
ts.startup = result
if ts.startup.StartResult.RealIP == "" {
mapping, err := ts.runner.GetNetworkAndAddresses(ts.startup.Container.ID)
require.NoError(ts.t, err, "failed to fetch network addresses to correct missing real IP address")
if len(network) == 0 {
require.Equal(ts.t, 1, len(mapping), "expected exactly one network address")
for network = range mapping {
// Because mapping is a map of network name->ip, we need
// to use the above range's assignment to get the name,
// as there is no other way of getting the keys of a map.
}
}
require.Contains(ts.t, mapping, network, "expected network to be part of the mapping")
ts.startup.StartResult.RealIP = mapping[network]
}
ts.log.Info(fmt.Sprintf("[dnsserv] Addresses of DNS resolver: local=%v / container=%v", ts.GetLocalAddr(), ts.GetRemoteAddr()))
}
func (ts *TestServer) buildNamedConf() string {
forwarders := "\n"
if len(ts.forwarders) > 0 {
forwarders = "\tforwarders {\n"
for _, forwarder := range ts.forwarders {
forwarders += "\t\t" + forwarder + ";\n"
}
forwarders += "\t};\n"
}
zones := "\n"
for _, domain := range ts.domains {
zones += fmt.Sprintf("zone \"%s\" {\n", domain)
zones += "\ttype primary;\n"
zones += fmt.Sprintf("\tfile \"%s.zone\";\n", domain)
zones += "\tallow-update {\n\t\tnone;\n\t};\n"
zones += "\tnotify no;\n"
zones += "};\n\n"
}
// Reverse lookups are not handles as they're not presently necessary.
cfg := `options {
directory "/var/cache/bind";
dnssec-validation no;
` + forwarders + `
};
` + zones
return cfg
}
func (ts *TestServer) buildZoneFile(target string) string {
// One second TTL by default to allow quick refreshes.
zone := "$TTL 1;\n"
ts.serial += 1
zone += fmt.Sprintf("@\tIN\tSOA\tns.%v.\troot.%v.\t(\n", target, target)
zone += fmt.Sprintf("\t\t\t%d;\n\t\t\t1;\n\t\t\t1;\n\t\t\t2;\n\t\t\t1;\n\t\t\t)\n\n", ts.serial)
zone += fmt.Sprintf("@\tIN\tNS\tns%d.%v.\n", ts.serial, target)
zone += fmt.Sprintf("ns%d.%v.\tIN\tA\t%v\n", ts.serial, target, "127.0.0.1")
for domain, records := range ts.records {
if !strings.HasSuffix(domain, target) {
continue
}
for recordType, values := range records {
for _, value := range values {
zone += fmt.Sprintf("%s.\tIN\t%s\t%s\n", domain, recordType, value)
}
}
}
return zone
}
func (ts *TestServer) pushNamedConf() {
contents := docker.NewBuildContext()
cfgPath := "/etc/bind/named.conf.options"
namedCfg := ts.buildNamedConf()
contents[cfgPath] = docker.PathContentsFromString(namedCfg)
contents[cfgPath].SetOwners(0, 142) // root, bind
ts.log.Info(fmt.Sprintf("Generated bind9 config (%s):\n%v\n", cfgPath, namedCfg))
err := ts.runner.CopyTo(ts.startup.Container.ID, "/", contents)
require.NoError(ts.t, err, "failed pushing updated named.conf.options to container")
}
func (ts *TestServer) pushZoneFiles() {
contents := docker.NewBuildContext()
for _, domain := range ts.domains {
path := "/var/cache/bind/" + domain + ".zone"
zoneFile := ts.buildZoneFile(domain)
contents[path] = docker.PathContentsFromString(zoneFile)
contents[path].SetOwners(0, 142) // root, bind
ts.log.Info(fmt.Sprintf("Generated bind9 zone file for %v (%s):\n%v\n", domain, path, zoneFile))
}
err := ts.runner.CopyTo(ts.startup.Container.ID, "/", contents)
require.NoError(ts.t, err, "failed pushing updated named.conf.options to container")
}
func (ts *TestServer) PushConfig() {
ts.lock.Lock()
defer ts.lock.Unlock()
_, _, _, err := ts.runner.RunCmdWithOutput(ts.ctx, ts.startup.Container.ID, []string{"rndc", "freeze"})
require.NoError(ts.t, err, "failed to freeze DNS config")
// There's two cases here:
//
// 1. We've added a new top-level domain name. Here, we want to make
// sure the new zone file is pushed before we push the reference
// to it.
// 2. We've just added a new. Here, the order doesn't matter, but
// mostly likely the second push will be a no-op.
ts.pushZoneFiles()
ts.pushNamedConf()
_, _, _, err = ts.runner.RunCmdWithOutput(ts.ctx, ts.startup.Container.ID, []string{"rndc", "thaw"})
require.NoError(ts.t, err, "failed to thaw DNS config")
// Wait until our config has taken.
corehelpers.RetryUntil(ts.t, 15*time.Second, func() error {
// bind reloads based on file mtime, touch files before starting
// to make sure it has been updated more recently than when the
// last update was written. Then issue a new SIGHUP.
for _, domain := range ts.domains {
path := "/var/cache/bind/" + domain + ".zone"
touchCmd := []string{"touch", path}
_, _, _, err := ts.runner.RunCmdWithOutput(ts.ctx, ts.startup.Container.ID, touchCmd)
if err != nil {
return fmt.Errorf("failed to update zone mtime: %w", err)
}
}
ts.runner.DockerAPI.ContainerKill(ts.ctx, ts.startup.Container.ID, "SIGHUP")
// Connect to our bind resolver.
resolver := &net.Resolver{
PreferGo: true,
StrictErrors: false,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: 10 * time.Second,
}
return d.DialContext(ctx, network, ts.GetLocalAddr())
},
}
// last domain has the given serial number, which also appears in the
// NS record so we can fetch it via Go.
lastDomain := ts.domains[len(ts.domains)-1]
records, err := resolver.LookupNS(ts.ctx, lastDomain)
if err != nil {
return fmt.Errorf("failed to lookup NS record for %v: %w", lastDomain, err)
}
if len(records) != 1 {
return fmt.Errorf("expected only 1 NS record for %v, got %v/%v", lastDomain, len(records), records)
}
expectedNS := fmt.Sprintf("ns%d.%v.", ts.serial, lastDomain)
if records[0].Host != expectedNS {
return fmt.Errorf("expected to find NS %v, got %v indicating reload hadn't completed", expectedNS, records[0])
}
return nil
})
}
func (ts *TestServer) GetLocalAddr() string {
return ts.startup.Config.Address()
}
func (ts *TestServer) GetRemoteAddr() string {
return fmt.Sprintf("%s:%d", ts.startup.StartResult.RealIP, 53)
}
func (ts *TestServer) AddDomain(domain string) {
ts.lock.Lock()
defer ts.lock.Unlock()
for _, existing := range ts.domains {
if existing == domain {
return
}
}
ts.domains = append(ts.domains, domain)
}
func (ts *TestServer) AddRecord(domain string, record string, value string) {
ts.lock.Lock()
defer ts.lock.Unlock()
foundDomain := false
for _, existing := range ts.domains {
if strings.HasSuffix(domain, existing) {
foundDomain = true
break
}
}
if !foundDomain {
ts.t.Fatalf("cannot add record %v/%v :: [%v] -- no domain zone matching (%v)", record, domain, value, ts.domains)
}
value = strings.TrimSpace(value)
if _, present := ts.records[domain]; !present {
ts.records[domain] = map[string][]string{}
}
if values, present := ts.records[domain][record]; present {
for _, candidate := range values {
if candidate == value {
// Already present; skip adding.
return
}
}
}
ts.records[domain][record] = append(ts.records[domain][record], value)
}
func (ts *TestServer) RemoveRecord(domain string, record string, value string) {
ts.lock.Lock()
defer ts.lock.Unlock()
foundDomain := false
for _, existing := range ts.domains {
if strings.HasSuffix(domain, existing) {
foundDomain = true
break
}
}
if !foundDomain {
// Not found.
return
}
value = strings.TrimSpace(value)
if _, present := ts.records[domain]; !present {
// Not found.
return
}
var remaining []string
if values, present := ts.records[domain][record]; present {
for _, candidate := range values {
if candidate != value {
remaining = append(remaining, candidate)
}
}
}
ts.records[domain][record] = remaining
}
func (ts *TestServer) RemoveRecordsOfTypeForDomain(domain string, record string) {
ts.lock.Lock()
defer ts.lock.Unlock()
foundDomain := false
for _, existing := range ts.domains {
if strings.HasSuffix(domain, existing) {
foundDomain = true
break
}
}
if !foundDomain {
// Not found.
return
}
if _, present := ts.records[domain]; !present {
// Not found.
return
}
delete(ts.records[domain], record)
}
func (ts *TestServer) RemoveRecordsForDomain(domain string) {
ts.lock.Lock()
defer ts.lock.Unlock()
foundDomain := false
for _, existing := range ts.domains {
if strings.HasSuffix(domain, existing) {
foundDomain = true
break
}
}
if !foundDomain {
// Not found.
return
}
if _, present := ts.records[domain]; !present {
// Not found.
return
}
ts.records[domain] = map[string][]string{}
}
func (ts *TestServer) RemoveAllRecords() {
ts.lock.Lock()
defer ts.lock.Unlock()
ts.records = map[string]map[string][]string{}
}
func (ts *TestServer) Cleanup() {
if ts.cleanup != nil {
ts.cleanup()
}
if ts.startup != nil && ts.startup.Cleanup != nil {
ts.startup.Cleanup()
}
}

View File

@ -1,753 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"testing"
"github.com/hashicorp/vault/api"
vaulthttp "github.com/hashicorp/vault/http"
vaultocsp "github.com/hashicorp/vault/sdk/helper/ocsp"
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
)
func TestIntegration_RotateRootUsesNext(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/rotate/internal",
Storage: s,
Data: map[string]interface{}{
"common_name": "test.com",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed rotate root")
require.NotNil(t, resp, "got nil response from rotate root")
require.False(t, resp.IsError(), "got an error from rotate root: %#v", resp)
issuerId1 := resp.Data["issuer_id"].(issuerID)
issuerName1 := resp.Data["issuer_name"]
require.NotEmpty(t, issuerId1, "issuer id was empty on initial rotate root command")
require.Equal(t, "next", issuerName1, "expected an issuer name of next on initial rotate root command")
// Call it again, we should get a new issuer id, but since next issuer_name is used we should get a blank value.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/rotate/internal",
Storage: s,
Data: map[string]interface{}{
"common_name": "test.com",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed rotate root")
require.NotNil(t, resp, "got nil response from rotate root")
require.False(t, resp.IsError(), "got an error from rotate root: %#v", resp)
issuerId2 := resp.Data["issuer_id"].(issuerID)
issuerName2 := resp.Data["issuer_name"]
require.NotEmpty(t, issuerId2, "issuer id was empty on second rotate root command")
require.NotEqual(t, issuerId1, issuerId2, "should have been different issuer ids")
require.Empty(t, issuerName2, "expected a blank issuer name on the second rotate root command")
// Call it again, making sure we can use our own name if desired.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/rotate/internal",
Storage: s,
Data: map[string]interface{}{
"common_name": "test.com",
"issuer_name": "next-cert",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed rotate root")
require.NotNil(t, resp, "got nil response from rotate root")
require.False(t, resp.IsError(), "got an error from rotate root: %#v", resp)
issuerId3 := resp.Data["issuer_id"].(issuerID)
issuerName3 := resp.Data["issuer_name"]
require.NotEmpty(t, issuerId3, "issuer id was empty on third rotate root command")
require.NotEqual(t, issuerId3, issuerId1, "should have been different issuer id from initial")
require.NotEqual(t, issuerId3, issuerId2, "should have been different issuer id from second call")
require.Equal(t, "next-cert", issuerName3, "expected an issuer name that we specified on third rotate root command")
}
func TestIntegration_ReplaceRootNormal(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
// generate roots
genTestRootCa(t, b, s)
issuerId2, _ := genTestRootCa(t, b, s)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/replace",
Storage: s,
Data: map[string]interface{}{
"default": issuerId2.String(),
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed replacing root")
require.NotNil(t, resp, "got nil response from replacing root")
require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp)
replacedIssuer := resp.Data["default"]
require.Equal(t, issuerId2, replacedIssuer, "expected return value to match issuer we set")
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "config/issuers",
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed replacing root")
require.NotNil(t, resp, "got nil response from replacing root")
require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp)
defaultIssuer := resp.Data["default"]
require.Equal(t, issuerId2, defaultIssuer, "expected default issuer to be updated")
}
func TestIntegration_ReplaceRootDefaultsToNext(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
// generate roots
genTestRootCa(t, b, s)
issuerId2, _ := genTestRootCaWithIssuerName(t, b, s, "next")
// Do not specify the default value to replace.
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/replace",
Storage: s,
Data: map[string]interface{}{},
MountPoint: "pki/",
})
require.NoError(t, err, "failed replacing root")
require.NotNil(t, resp, "got nil response from replacing root")
require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp)
replacedIssuer := resp.Data["default"]
require.Equal(t, issuerId2, replacedIssuer, "expected return value to match the 'next' issuer we set")
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "config/issuers",
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed replacing root")
require.NotNil(t, resp, "got nil response from replacing root")
require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp)
defaultIssuer := resp.Data["default"]
require.Equal(t, issuerId2, defaultIssuer, "expected default issuer to be updated")
}
func TestIntegration_ReplaceRootBadIssuer(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
// generate roots
genTestRootCa(t, b, s)
genTestRootCa(t, b, s)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/replace",
Storage: s,
Data: map[string]interface{}{
"default": "a-bad-issuer-id",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed replacing root, should have been an error in the response.")
require.NotNil(t, resp, "got nil response from replacing root")
require.True(t, resp.IsError(), "did not get an error from replacing root: %#v", resp)
// Make sure we trap replacing with default.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/replace",
Storage: s,
Data: map[string]interface{}{
"default": "default",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed replacing root, should have been an error in the response.")
require.NotNil(t, resp, "got nil response from replacing root")
require.True(t, resp.IsError(), "did not get an error from replacing root: %#v", resp)
// Make sure we trap replacing with blank string.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/replace",
Storage: s,
Data: map[string]interface{}{
"default": "",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed replacing root, should have been an error in the response.")
require.NotNil(t, resp, "got nil response from replacing root")
require.True(t, resp.IsError(), "did not get an error from replacing root: %#v", resp)
}
func TestIntegration_SetSignedWithBackwardsPemBundles(t *testing.T) {
t.Parallel()
rootBackend, rootStorage := CreateBackendWithStorage(t)
intBackend, intStorage := CreateBackendWithStorage(t)
// generate root
resp, err := rootBackend.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "issuers/generate/root/internal",
Storage: rootStorage,
Data: map[string]interface{}{
"common_name": "test.com",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed generating root ca")
require.NotNil(t, resp, "got nil response from generating root ca")
require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp)
rootCert := resp.Data["certificate"].(string)
schema.ValidateResponse(t, schema.GetResponseSchema(t, rootBackend.Route("issuers/generate/root/internal"), logical.UpdateOperation), resp, true)
// generate intermediate
resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "issuers/generate/intermediate/internal",
Storage: intStorage,
Data: map[string]interface{}{
"common_name": "test.com",
},
MountPoint: "pki-int/",
})
require.NoError(t, err, "failed generating int ca")
require.NotNil(t, resp, "got nil response from generating int ca")
require.False(t, resp.IsError(), "got an error from generating int ca: %#v", resp)
intCsr := resp.Data["csr"].(string)
// sign csr
resp, err = rootBackend.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "root/sign-intermediate",
Storage: rootStorage,
Data: map[string]interface{}{
"csr": intCsr,
"format": "pem_bundle",
},
MountPoint: "pki/",
})
require.NoError(t, err, "failed generating root ca")
require.NotNil(t, resp, "got nil response from generating root ca")
require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp)
intCert := resp.Data["certificate"].(string)
// Send in the chain backwards now and make sure we link intCert as default.
resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "intermediate/set-signed",
Storage: intStorage,
Data: map[string]interface{}{
"certificate": rootCert + "\n" + intCert + "\n",
},
MountPoint: "pki-int/",
})
require.NoError(t, err, "failed generating root ca")
require.NotNil(t, resp, "got nil response from generating root ca")
require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp)
// setup role
resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/example",
Storage: intStorage,
Data: map[string]interface{}{
"allowed_domains": "example.com",
"allow_subdomains": "true",
"max_ttl": "1h",
},
MountPoint: "pki-int/",
})
require.NoError(t, err, "failed setting up role example")
require.NotNil(t, resp, "got nil response from setting up role example: %#v", resp)
schema.ValidateResponse(t, schema.GetResponseSchema(t, intBackend.Route("roles/example"), logical.UpdateOperation), resp, true)
// Issue cert
resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "issue/example",
Storage: intStorage,
Data: map[string]interface{}{
"common_name": "test.example.com",
"ttl": "5m",
},
MountPoint: "pki-int/",
})
require.NoError(t, err, "failed issuing a leaf cert from int ca")
require.NotNil(t, resp, "got nil response issuing a leaf cert from int ca")
require.False(t, resp.IsError(), "got an error issuing a leaf cert from int ca: %#v", resp)
schema.ValidateResponse(t, schema.GetResponseSchema(t, intBackend.Route("issue/example"), logical.UpdateOperation), resp, true)
}
func TestIntegration_CSRGeneration(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
testCases := []struct {
keyType string
usePss bool
keyBits int
sigBits int
expectedPublicKeyType crypto.PublicKey
expectedSignature x509.SignatureAlgorithm
}{
{"rsa", false, 2048, 0, &rsa.PublicKey{}, x509.SHA256WithRSA},
{"rsa", false, 2048, 384, &rsa.PublicKey{}, x509.SHA384WithRSA},
// Add back once https://github.com/golang/go/issues/45990 is fixed.
// {"rsa", true, 2048, 0, &rsa.PublicKey{}, x509.SHA256WithRSAPSS},
// {"rsa", true, 2048, 512, &rsa.PublicKey{}, x509.SHA512WithRSAPSS},
{"ec", false, 224, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA256},
{"ec", false, 256, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA256},
{"ec", false, 384, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA384},
{"ec", false, 521, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA512},
{"ec", false, 521, 224, &ecdsa.PublicKey{}, x509.ECDSAWithSHA512}, // We ignore signature_bits for ec
{"ed25519", false, 0, 0, ed25519.PublicKey{}, x509.PureEd25519}, // We ignore both fields for ed25519
}
for _, tc := range testCases {
keyTypeName := tc.keyType
if tc.usePss {
keyTypeName = tc.keyType + "-pss"
}
testName := fmt.Sprintf("%s-%d-%d", keyTypeName, tc.keyBits, tc.sigBits)
t.Run(testName, func(t *testing.T) {
resp, err := CBWrite(b, s, "intermediate/generate/internal", map[string]interface{}{
"common_name": "myint.com",
"key_type": tc.keyType,
"key_bits": tc.keyBits,
"signature_bits": tc.sigBits,
"use_pss": tc.usePss,
})
requireSuccessNonNilResponse(t, resp, err)
requireFieldsSetInResp(t, resp, "csr")
csrString := resp.Data["csr"].(string)
pemBlock, _ := pem.Decode([]byte(csrString))
require.NotNil(t, pemBlock, "failed to parse returned csr pem block")
csr, err := x509.ParseCertificateRequest(pemBlock.Bytes)
require.NoError(t, err, "failed parsing certificate request")
require.Equal(t, tc.expectedSignature, csr.SignatureAlgorithm,
"Expected %s, got %s", tc.expectedSignature.String(), csr.SignatureAlgorithm.String())
require.IsType(t, tc.expectedPublicKeyType, csr.PublicKey)
})
}
}
func TestIntegration_AutoIssuer(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
// Generate two roots. The first should become default under the existing
// behavior; when we update the config and generate a second, it should
// take over as default. Deleting the first and re-importing it will make
// it default again, and then disabling the option and removing and
// reimporting the second and creating a new root won't affect it again.
resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "Root X1",
"issuer_name": "root-1",
"key_type": "ec",
})
requireSuccessNonNilResponse(t, resp, err)
issuerIdOne := resp.Data["issuer_id"]
require.NotEmpty(t, issuerIdOne)
certOne := resp.Data["certificate"]
require.NotEmpty(t, certOne)
resp, err = CBRead(b, s, "config/issuers")
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, issuerIdOne, resp.Data["default"])
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.ReadOperation), resp, true)
// Enable the new config option.
resp, err = CBWrite(b, s, "config/issuers", map[string]interface{}{
"default": issuerIdOne,
"default_follows_latest_issuer": true,
})
require.NoError(t, err)
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.UpdateOperation), resp, true)
// Now generate the second root; it should become default.
resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "Root X2",
"issuer_name": "root-2",
"key_type": "ec",
})
requireSuccessNonNilResponse(t, resp, err)
issuerIdTwo := resp.Data["issuer_id"]
require.NotEmpty(t, issuerIdTwo)
certTwo := resp.Data["certificate"]
require.NotEmpty(t, certTwo)
resp, err = CBRead(b, s, "config/issuers")
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, issuerIdTwo, resp.Data["default"])
// Deleting the first shouldn't affect the default issuer.
_, err = CBDelete(b, s, "issuer/root-1")
require.NoError(t, err)
resp, err = CBRead(b, s, "config/issuers")
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, issuerIdTwo, resp.Data["default"])
// But reimporting it should update it to the new issuer's value.
resp, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{
"pem_bundle": certOne,
})
requireSuccessNonNilResponse(t, resp, err)
issuerIdOneReimported := issuerID(resp.Data["imported_issuers"].([]string)[0])
resp, err = CBRead(b, s, "config/issuers")
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, issuerIdOneReimported, resp.Data["default"])
// Now update the config to disable this option again.
_, err = CBWrite(b, s, "config/issuers", map[string]interface{}{
"default": issuerIdOneReimported,
"default_follows_latest_issuer": false,
})
require.NoError(t, err)
// Generating a new root shouldn't update the default.
resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "Root X3",
"issuer_name": "root-3",
"key_type": "ec",
})
requireSuccessNonNilResponse(t, resp, err)
issuerIdThree := resp.Data["issuer_id"]
require.NotEmpty(t, issuerIdThree)
resp, err = CBRead(b, s, "config/issuers")
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, issuerIdOneReimported, resp.Data["default"])
// Deleting and re-importing root 2 should also not affect it.
_, err = CBDelete(b, s, "issuer/root-2")
require.NoError(t, err)
resp, err = CBRead(b, s, "config/issuers")
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, issuerIdOneReimported, resp.Data["default"])
resp, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{
"pem_bundle": certTwo,
})
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, 1, len(resp.Data["imported_issuers"].([]string)))
resp, err = CBRead(b, s, "config/issuers")
requireSuccessNonNilResponse(t, resp, err)
require.Equal(t, issuerIdOneReimported, resp.Data["default"])
}
// TestLDAPAiaCrlUrls validates we can properly handle CRL urls that are ldap based.
func TestLDAPAiaCrlUrls(t *testing.T) {
t.Parallel()
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
NumCores: 1,
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
singleCore := cluster.Cores[0]
vault.TestWaitActive(t, singleCore.Core)
client := singleCore.Client
mountPKIEndpoint(t, client, "pki")
// Attempt multiple urls
crls := []string{
"ldap://ldap.example.com/cn=example%20CA,dc=example,dc=com?certificateRevocationList;binary",
"ldap://ldap.example.com/cn=CA,dc=example,dc=com?authorityRevocationList;binary",
}
_, err := client.Logical().Write("pki/config/urls", map[string]interface{}{
"crl_distribution_points": crls,
})
require.NoError(t, err)
resp, err := client.Logical().Read("pki/config/urls")
require.NoError(t, err, "failed reading config/urls")
require.NotNil(t, resp, "resp was nil")
require.NotNil(t, resp.Data, "data within response was nil")
require.NotEmpty(t, resp.Data["crl_distribution_points"], "crl_distribution_points was nil within data")
require.Len(t, resp.Data["crl_distribution_points"], len(crls))
for _, crlVal := range crls {
require.Contains(t, resp.Data["crl_distribution_points"], crlVal)
}
resp, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"ttl": "40h",
"common_name": "Root R1",
"key_type": "ec",
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.NotEmpty(t, resp.Data["issuer_id"])
rootIssuerId := resp.Data["issuer_id"].(string)
_, err = client.Logical().Write("pki/roles/example-root", map[string]interface{}{
"allowed_domains": "example.com",
"allow_subdomains": "true",
"max_ttl": "1h",
"key_type": "ec",
"issuer_ref": rootIssuerId,
})
require.NoError(t, err)
resp, err = client.Logical().Write("pki/issue/example-root", map[string]interface{}{
"common_name": "test.example.com",
"ttl": "5m",
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.NotEmpty(t, resp.Data["certificate"])
certPEM := resp.Data["certificate"].(string)
certBlock, _ := pem.Decode([]byte(certPEM))
require.NotNil(t, certBlock)
cert, err := x509.ParseCertificate(certBlock.Bytes)
require.NoError(t, err)
require.EqualValues(t, crls, cert.CRLDistributionPoints)
}
func TestIntegrationOCSPClientWithPKI(t *testing.T) {
t.Parallel()
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
cores := cluster.Cores
vault.TestWaitActive(t, cores[0].Core)
client := cores[0].Client
err := client.Sys().Mount("pki", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "32h",
},
})
require.NoError(t, err)
resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"ttl": "40h",
"common_name": "Root R1",
"key_type": "ec",
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.NotEmpty(t, resp.Data["issuer_id"])
rootIssuerId := resp.Data["issuer_id"].(string)
// Set URLs pointing to the issuer.
_, err = client.Logical().Write("pki/config/cluster", map[string]interface{}{
"path": client.Address() + "/v1/pki",
"aia_path": client.Address() + "/v1/pki",
})
require.NoError(t, err)
_, err = client.Logical().Write("pki/config/urls", map[string]interface{}{
"enable_templating": true,
"crl_distribution_points": "{{cluster_aia_path}}/issuer/{{issuer_id}}/crl/der",
"issuing_certificates": "{{cluster_aia_path}}/issuer/{{issuer_id}}/der",
"ocsp_servers": "{{cluster_aia_path}}/ocsp",
})
require.NoError(t, err)
// Build an intermediate CA
resp, err = client.Logical().Write("pki/intermediate/generate/internal", map[string]interface{}{
"common_name": "Int X1",
"key_type": "ec",
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.NotEmpty(t, resp.Data["csr"])
intermediateCSR := resp.Data["csr"].(string)
resp, err = client.Logical().Write("pki/root/sign-intermediate", map[string]interface{}{
"csr": intermediateCSR,
"ttl": "20h",
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.NotEmpty(t, resp.Data["certificate"])
intermediateCert := resp.Data["certificate"]
resp, err = client.Logical().Write("pki/intermediate/set-signed", map[string]interface{}{
"certificate": intermediateCert,
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.NotEmpty(t, resp.Data["imported_issuers"])
rawImportedIssuers := resp.Data["imported_issuers"].([]interface{})
require.Equal(t, len(rawImportedIssuers), 1)
importedIssuer := rawImportedIssuers[0].(string)
require.NotEmpty(t, importedIssuer)
// Set intermediate as default.
_, err = client.Logical().Write("pki/config/issuers", map[string]interface{}{
"default": importedIssuer,
})
require.NoError(t, err)
// Setup roles for root, intermediate.
_, err = client.Logical().Write("pki/roles/example-root", map[string]interface{}{
"allowed_domains": "example.com",
"allow_subdomains": "true",
"max_ttl": "1h",
"key_type": "ec",
"issuer_ref": rootIssuerId,
})
require.NoError(t, err)
_, err = client.Logical().Write("pki/roles/example-int", map[string]interface{}{
"allowed_domains": "example.com",
"allow_subdomains": "true",
"max_ttl": "1h",
"key_type": "ec",
})
require.NoError(t, err)
// Issue certs and validate them against OCSP.
for _, path := range []string{"pki/issue/example-int", "pki/issue/example-root"} {
t.Logf("Validating against path: %v", path)
resp, err = client.Logical().Write(path, map[string]interface{}{
"common_name": "test.example.com",
"ttl": "5m",
})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
require.NotEmpty(t, resp.Data["certificate"])
require.NotEmpty(t, resp.Data["issuing_ca"])
require.NotEmpty(t, resp.Data["serial_number"])
certPEM := resp.Data["certificate"].(string)
certBlock, _ := pem.Decode([]byte(certPEM))
require.NotNil(t, certBlock)
cert, err := x509.ParseCertificate(certBlock.Bytes)
require.NoError(t, err)
require.NotNil(t, cert)
issuerPEM := resp.Data["issuing_ca"].(string)
issuerBlock, _ := pem.Decode([]byte(issuerPEM))
require.NotNil(t, issuerBlock)
issuer, err := x509.ParseCertificate(issuerBlock.Bytes)
require.NoError(t, err)
require.NotNil(t, issuer)
serialNumber := resp.Data["serial_number"].(string)
testLogger := hclog.New(hclog.DefaultOptions)
conf := &vaultocsp.VerifyConfig{
OcspFailureMode: vaultocsp.FailOpenFalse,
ExtraCas: []*x509.Certificate{cluster.CACert},
}
ocspClient := vaultocsp.New(func() hclog.Logger {
return testLogger
}, 10)
_, err = client.Logical().Write("pki/revoke", map[string]interface{}{
"serial_number": serialNumber,
})
require.NoError(t, err)
err = ocspClient.VerifyLeafCertificate(context.Background(), cert, issuer, conf)
require.Error(t, err)
}
}
func genTestRootCa(t *testing.T, b *backend, s logical.Storage) (issuerID, keyID) {
return genTestRootCaWithIssuerName(t, b, s, "")
}
func genTestRootCaWithIssuerName(t *testing.T, b *backend, s logical.Storage, issuerName string) (issuerID, keyID) {
data := map[string]interface{}{
"common_name": "test.com",
}
if len(issuerName) > 0 {
data["issuer_name"] = issuerName
}
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "issuers/generate/root/internal",
Storage: s,
Data: data,
MountPoint: "pki/",
})
require.NoError(t, err, "failed generating root ca")
require.NotNil(t, resp, "got nil response from generating root ca")
require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp)
issuerId := resp.Data["issuer_id"].(issuerID)
keyId := resp.Data["key_id"].(keyID)
require.NotEmpty(t, issuerId, "returned issuer id was empty")
require.NotEmpty(t, keyId, "returned key id was empty")
return issuerId, keyId
}

View File

@ -1,142 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"net"
"testing"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
// TestACME_ValidateIdentifiersAgainstRole Verify the ACME order creation
// function verifies somewhat the identifiers that were provided have a
// decent chance of being allowed by the selected role.
func TestACME_ValidateIdentifiersAgainstRole(t *testing.T) {
b, _ := CreateBackendWithStorage(t)
tests := []struct {
name string
role *roleEntry
identifiers []*ACMEIdentifier
expectErr bool
}{
{
name: "verbatim-role-allows-dns-ip",
role: buildSignVerbatimRoleWithNoData(nil),
identifiers: _buildACMEIdentifiers("test.com", "127.0.0.1"),
expectErr: false,
},
{
name: "default-role-does-not-allow-dns",
role: buildTestRole(t, nil),
identifiers: _buildACMEIdentifiers("www.test.com"),
expectErr: true,
},
{
name: "default-role-allows-ip",
role: buildTestRole(t, nil),
identifiers: _buildACMEIdentifiers("192.168.0.1"),
expectErr: false,
},
{
name: "disable-ip-sans-forbids-ip",
role: buildTestRole(t, map[string]interface{}{"allow_ip_sans": false}),
identifiers: _buildACMEIdentifiers("192.168.0.1"),
expectErr: true,
},
{
name: "role-no-wildcards-allowed-without",
role: buildTestRole(t, map[string]interface{}{
"allow_subdomains": true,
"allow_bare_domains": true,
"allowed_domains": []string{"test.com"},
"allow_wildcard_certificates": false,
}),
identifiers: _buildACMEIdentifiers("www.test.com", "test.com"),
expectErr: false,
},
{
name: "role-no-wildcards-allowed-with-wildcard",
role: buildTestRole(t, map[string]interface{}{
"allow_subdomains": true,
"allowed_domains": []string{"test.com"},
"allow_wildcard_certificates": false,
}),
identifiers: _buildACMEIdentifiers("*.test.com"),
expectErr: true,
},
{
name: "role-wildcards-allowed-with-wildcard",
role: buildTestRole(t, map[string]interface{}{
"allow_subdomains": true,
"allowed_domains": []string{"test.com"},
"allow_wildcard_certificates": true,
}),
identifiers: _buildACMEIdentifiers("*.test.com"),
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := b.validateIdentifiersAgainstRole(tt.role, tt.identifiers)
if tt.expectErr {
require.Error(t, err, "validateIdentifiersAgainstRole(%v, %v)", tt.role.ToResponseData(), tt.identifiers)
// If we did return an error if should be classified as a ErrRejectedIdentifier
require.ErrorIs(t, err, ErrRejectedIdentifier)
} else {
require.NoError(t, err, "validateIdentifiersAgainstRole(%v, %v)", tt.role.ToResponseData(), tt.identifiers)
}
})
}
}
func _buildACMEIdentifiers(values ...string) []*ACMEIdentifier {
var identifiers []*ACMEIdentifier
for _, value := range values {
identifiers = append(identifiers, _buildACMEIdentifier(value))
}
return identifiers
}
func _buildACMEIdentifier(val string) *ACMEIdentifier {
ip := net.ParseIP(val)
if ip == nil {
identifier := &ACMEIdentifier{Type: "dns", Value: val, OriginalValue: val, IsWildcard: false}
_, _, _ = identifier.MaybeParseWildcard()
return identifier
}
return &ACMEIdentifier{Type: "ip", Value: val, OriginalValue: val, IsWildcard: false}
}
// Easily allow tests to create valid roles with proper defaults, since we don't have an easy
// way to generate roles with proper defaults, go through the createRole handler with the handlers
// field data so we pickup all the defaults specified there.
func buildTestRole(t *testing.T, config map[string]interface{}) *roleEntry {
b, s := CreateBackendWithStorage(t)
path := pathRoles(b)
fields := path.Fields
if config == nil {
config = map[string]interface{}{}
}
if _, exists := config["name"]; !exists {
config["name"] = genUuid()
}
_, err := b.pathRoleCreate(ctx, &logical.Request{Storage: s}, &framework.FieldData{Raw: config, Schema: fields})
require.NoError(t, err, "failed generating role with config %v", config)
role, err := b.getRole(ctx, s, config["name"].(string))
require.NoError(t, err, "failed loading stored role")
return role
}

File diff suppressed because it is too large Load Diff

View File

@ -1,132 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"crypto/rand"
"crypto/rsa"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestAcmeConfig(t *testing.T) {
cluster, client, _ := setupAcmeBackend(t)
defer cluster.Cleanup()
cases := []struct {
name string
AcmeConfig map[string]interface{}
prefixUrl string
validConfig bool
works bool
}{
{"unspecified-root", map[string]interface{}{
"enabled": true,
"allowed_issuers": "*",
"allowed_roles": "*",
"dns_resolver": "",
"eab_policy_name": "",
}, "acme/", true, true},
{"bad-policy-root", map[string]interface{}{
"enabled": true,
"allowed_issuers": "*",
"allowed_roles": "*",
"default_directory_policy": "bad",
"dns_resolver": "",
"eab_policy_name": "",
}, "acme/", false, false},
{"forbid-root", map[string]interface{}{
"enabled": true,
"allowed_issuers": "*",
"allowed_roles": "*",
"default_directory_policy": "forbid",
"dns_resolver": "",
"eab_policy_name": "",
}, "acme/", true, false},
{"sign-verbatim-root", map[string]interface{}{
"enabled": true,
"allowed_issuers": "*",
"allowed_roles": "*",
"default_directory_policy": "sign-verbatim",
"dns_resolver": "",
"eab_policy_name": "",
}, "acme/", true, true},
{"role-root", map[string]interface{}{
"enabled": true,
"allowed_issuers": "*",
"allowed_roles": "*",
"default_directory_policy": "role:exists",
"dns_resolver": "",
"eab_policy_name": "",
}, "acme/", true, true},
{"bad-role-root", map[string]interface{}{
"enabled": true,
"allowed_issuers": "*",
"allowed_roles": "*",
"default_directory_policy": "role:notgood",
"dns_resolver": "",
"eab_policy_name": "",
}, "acme/", false, true},
{"disallowed-role-root", map[string]interface{}{
"enabled": true,
"allowed_issuers": "*",
"allowed_roles": "good",
"default_directory_policy": "role:exists",
"dns_resolver": "",
"eab_policy_name": "",
}, "acme/", false, false},
}
roleConfig := map[string]interface{}{
"issuer_ref": "default",
"allowed_domains": "example.com",
"allow_subdomains": true,
"max_ttl": "720h",
}
testCtx := context.Background()
for _, tc := range cases {
deadline := time.Now().Add(1 * time.Minute)
subTestCtx, _ := context.WithDeadline(testCtx, deadline)
_, err := client.Logical().WriteWithContext(subTestCtx, "pki/roles/exists", roleConfig)
require.NoError(t, err)
_, err = client.Logical().WriteWithContext(subTestCtx, "pki/roles/good", roleConfig)
require.NoError(t, err)
t.Run(tc.name, func(t *testing.T) {
_, err := client.Logical().WriteWithContext(subTestCtx, "pki/config/acme", tc.AcmeConfig)
if tc.validConfig {
require.NoError(t, err)
} else {
require.Error(t, err)
return
}
_, err = client.Logical().ReadWithContext(subTestCtx, "pki/acme/directory")
if tc.works {
require.NoError(t, err)
baseAcmeURL := "/v1/pki/" + tc.prefixUrl
accountKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err, "failed creating rsa key")
acmeClient := getAcmeClientForCluster(t, cluster, baseAcmeURL, accountKey)
// Create new account
_, err = acmeClient.Discover(subTestCtx)
require.NoError(t, err, "failed acme discovery call")
} else {
require.Error(t, err, "Acme Configuration should prevent usage")
}
t.Logf("Completed case %v", tc.name)
})
}
}

View File

@ -1,441 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"context"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"testing"
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
func TestPKI_PathManageKeys_GenerateInternalKeys(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
tests := []struct {
name string
keyType string
keyBits []int
wantLogicalErr bool
}{
{"all-defaults", "", []int{0}, false},
{"rsa", "rsa", []int{0, 2048, 3072, 4096}, false},
{"ec", "ec", []int{0, 224, 256, 384, 521}, false},
{"ed25519", "ed25519", []int{0}, false},
{"error-rsa", "rsa", []int{-1, 343444}, true},
{"error-ec", "ec", []int{-1, 3434324}, true},
{"error-bad-type", "dskjfkdsfjdkf", []int{0}, true},
}
for _, tt := range tests {
tt := tt
for _, keyBitParam := range tt.keyBits {
keyName := fmt.Sprintf("%s-%d", tt.name, keyBitParam)
t.Run(keyName, func(t *testing.T) {
data := make(map[string]interface{})
if tt.keyType != "" {
data["key_type"] = tt.keyType
}
if keyBitParam != 0 {
data["key_bits"] = keyBitParam
}
keyName = genUuid() + "-" + tt.keyType + "-key-name"
data["key_name"] = keyName
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/internal",
Storage: s,
Data: data,
MountPoint: "pki/",
})
require.NoError(t, err,
"Failed generating key with values key_type:%s key_bits:%d key_name:%s", tt.keyType, keyBitParam, keyName)
require.NotNil(t, resp,
"Got nil response generating key with values key_type:%s key_bits:%d key_name:%s", tt.keyType, keyBitParam, keyName)
if tt.wantLogicalErr {
require.True(t, resp.IsError(), "expected logical error but the request passed:\n%#v", resp)
} else {
require.False(t, resp.IsError(),
"Got logical error response when not expecting one, "+
"generating key with values key_type:%s key_bits:%d key_name:%s\n%s", tt.keyType, keyBitParam, keyName, resp.Error())
// Special case our all-defaults
if tt.keyType == "" {
tt.keyType = "rsa"
}
require.Equal(t, tt.keyType, resp.Data["key_type"], "key_type field contained an invalid type")
require.NotEmpty(t, resp.Data["key_id"], "returned an empty key_id field, should never happen")
require.Equal(t, keyName, resp.Data["key_name"], "key name was not processed correctly")
require.Nil(t, resp.Data["private_key"], "private_key field should not appear in internal generation type.")
}
})
}
}
}
func TestPKI_PathManageKeys_GenerateExportedKeys(t *testing.T) {
t.Parallel()
// We tested a lot of the logic above within the internal test, so just make sure we honor the exported contract
b, s := CreateBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/exported",
Storage: s,
Data: map[string]interface{}{
"key_type": "ec",
"key_bits": 224,
},
MountPoint: "pki/",
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("keys/generate/exported"), logical.UpdateOperation), resp, true)
require.NoError(t, err, "Failed generating exported key")
require.NotNil(t, resp, "Got nil response generating exported key")
require.Equal(t, "ec", resp.Data["key_type"], "key_type field contained an invalid type")
require.NotEmpty(t, resp.Data["key_id"], "returned an empty key_id field, should never happen")
require.Empty(t, resp.Data["key_name"], "key name should have been empty but was not")
require.NotEmpty(t, resp.Data["private_key"], "private_key field should not be empty in exported generation type.")
// Make sure we can decode our private key as expected
keyData := resp.Data["private_key"].(string)
block, rest := pem.Decode([]byte(keyData))
require.Empty(t, rest, "should not have had any trailing data")
require.NotEmpty(t, block, "failed decoding pem block")
key, err := x509.ParseECPrivateKey(block.Bytes)
require.NoError(t, err, "failed parsing pem block as ec private key")
require.Equal(t, elliptic.P224(), key.Curve, "got unexpected curve value in returned private key")
}
func TestPKI_PathManageKeys_ImportKeyBundle(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
bundle1, err := certutil.CreateKeyBundle("ec", 224, rand.Reader)
require.NoError(t, err, "failed generating an ec key bundle")
bundle2, err := certutil.CreateKeyBundle("rsa", 2048, rand.Reader)
require.NoError(t, err, "failed generating an rsa key bundle")
pem1, err := bundle1.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting ec key to pem")
pem2, err := bundle2.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting rsa key to pem")
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": pem1,
},
MountPoint: "pki/",
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("keys/import"), logical.UpdateOperation), resp, true)
require.NoError(t, err, "Failed importing ec key")
require.NotNil(t, resp, "Got nil response importing ec key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for ec import response was empty")
require.Equal(t, "my-ec-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.ECPrivateKey, resp.Data["key_type"])
keyId1 := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-rsa-key",
"pem_bundle": pem2,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing rsa key")
require.NotNil(t, resp, "Got nil response importing rsa key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for rsa import response was empty")
require.Equal(t, "my-rsa-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.RSAPrivateKey, resp.Data["key_type"])
keyId2 := resp.Data["key_id"].(keyID)
require.NotEqual(t, keyId1, keyId2)
// Attempt to reimport the same key with a different name.
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-new-ec-key",
"pem_bundle": pem1,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing the same ec key")
require.NotNil(t, resp, "Got nil response importing the same ec key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for ec import response was empty")
// Note we should receive back the original name, not the new updated name.
require.Equal(t, "my-ec-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.ECPrivateKey, resp.Data["key_type"])
keyIdReimport := resp.Data["key_id"]
require.Equal(t, keyId1, keyIdReimport, "the re-imported key did not return the same key id")
// Make sure we can not reuse an existing name across different keys.
bundle3, err := certutil.CreateKeyBundle("ec", 224, rand.Reader)
require.NoError(t, err, "failed generating an ec key bundle")
pem3, err := bundle3.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting rsa key to pem")
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": pem3,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing the same ec key")
require.NotNil(t, resp, "Got nil response importing the same ec key")
require.True(t, resp.IsError(), "should have received an error response importing a key with a re-used name")
// Delete the key to make sure re-importing gets another ID
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId2.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting keyId 2")
require.Nil(t, resp, "Got non-nil response deleting the key: %#v", resp)
// Deleting a non-existent key should be okay...
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId2.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting keyId 2")
require.Nil(t, resp, "Got non-nil response deleting the key: %#v", resp)
// Let's reimport key 2 post-deletion to make sure we re-generate a new key id
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-rsa-key",
"pem_bundle": pem2,
},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed importing rsa key")
require.NotNil(t, resp, "Got nil response importing rsa key")
require.False(t, resp.IsError(), "received an error response: %v", resp.Error())
require.NotEmpty(t, resp.Data["key_id"], "key id for rsa import response was empty")
require.Equal(t, "my-rsa-key", resp.Data["key_name"], "key_name was incorrect for ec key")
require.Equal(t, certutil.RSAPrivateKey, resp.Data["key_type"])
keyId2Reimport := resp.Data["key_id"].(keyID)
require.NotEqual(t, keyId2, keyId2Reimport, "re-importing key 2 did not generate a new key id")
}
func TestPKI_PathManageKeys_DeleteDefaultKeyWarns(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/internal",
Storage: s,
Data: map[string]interface{}{"key_type": "ec"},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed generating key")
require.NotNil(t, resp, "Got nil response generating key")
require.False(t, resp.IsError(), "resp contained errors generating key: %#v", resp.Error())
keyId := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting default key")
require.NotNil(t, resp, "Got nil response deleting the default key")
require.False(t, resp.IsError(), "expected no errors deleting default key: %#v", resp.Error())
require.NotEmpty(t, resp.Warnings, "expected warnings to be populated on deleting default key")
}
func TestPKI_PathManageKeys_DeleteUsedKeyFails(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "issuers/generate/root/internal",
Storage: s,
Data: map[string]interface{}{"common_name": "test.com"},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed generating issuer")
require.NotNil(t, resp, "Got nil response generating issuer")
require.False(t, resp.IsError(), "resp contained errors generating issuer: %#v", resp.Error())
keyId := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.DeleteOperation,
Path: "key/" + keyId.String(),
Storage: s,
MountPoint: "pki/",
})
require.NoError(t, err, "failed deleting key used by an issuer")
require.NotNil(t, resp, "Got nil response deleting key used by an issuer")
require.True(t, resp.IsError(), "expected an error deleting key used by an issuer")
}
func TestPKI_PathManageKeys_UpdateKeyDetails(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/generate/internal",
Storage: s,
Data: map[string]interface{}{"key_type": "ec"},
MountPoint: "pki/",
})
require.NoError(t, err, "Failed generating key")
require.NotNil(t, resp, "Got nil response generating key")
require.False(t, resp.IsError(), "resp contained errors generating key: %#v", resp.Error())
keyId := resp.Data["key_id"].(keyID)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "key/" + keyId.String(),
Storage: s,
Data: map[string]interface{}{"key_name": "new-name"},
MountPoint: "pki/",
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("key/"+keyId.String()), logical.UpdateOperation), resp, true)
require.NoError(t, err, "failed updating key with new name")
require.NotNil(t, resp, "Got nil response updating key with new name")
require.False(t, resp.IsError(), "unexpected error updating key with new name: %#v", resp.Error())
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ReadOperation,
Path: "key/" + keyId.String(),
Storage: s,
MountPoint: "pki/",
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("key/"+keyId.String()), logical.ReadOperation), resp, true)
require.NoError(t, err, "failed reading key after name update")
require.NotNil(t, resp, "Got nil response reading key after name update")
require.False(t, resp.IsError(), "unexpected error reading key: %#v", resp.Error())
keyName := resp.Data["key_name"].(string)
require.Equal(t, "new-name", keyName, "failed to update key_name expected: new-name was: %s", keyName)
// Make sure we do not allow updates to invalid name values
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "key/" + keyId.String(),
Storage: s,
Data: map[string]interface{}{"key_name": "a-bad\\-name"},
MountPoint: "pki/",
})
require.NoError(t, err, "failed updating key with a bad name")
require.NotNil(t, resp, "Got nil response updating key with a bad name")
require.True(t, resp.IsError(), "expected an error updating key with a bad name, but did not get one.")
}
func TestPKI_PathManageKeys_ImportKeyBundleBadData(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": "this-is-not-a-pem-bundle",
},
MountPoint: "pki/",
})
require.NoError(t, err, "got a 500 error type response from a bad pem bundle")
require.NotNil(t, resp, "Got nil response importing a bad pem bundle")
require.True(t, resp.IsError(), "should have received an error response importing a bad pem bundle")
// Make sure we also bomb on a proper certificate
bundle := genCertBundle(t, b, s)
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"pem_bundle": bundle.Certificate,
},
MountPoint: "pki/",
})
require.NoError(t, err, "got a 500 error type response from a certificate pem bundle")
require.NotNil(t, resp, "Got nil response importing a certificate bundle")
require.True(t, resp.IsError(), "should have received an error response importing a certificate pem bundle")
}
func TestPKI_PathManageKeys_ImportKeyRejectsMultipleKeys(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
bundle1, err := certutil.CreateKeyBundle("ec", 224, rand.Reader)
require.NoError(t, err, "failed generating an ec key bundle")
bundle2, err := certutil.CreateKeyBundle("rsa", 2048, rand.Reader)
require.NoError(t, err, "failed generating an rsa key bundle")
pem1, err := bundle1.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting ec key to pem")
pem2, err := bundle2.ToPrivateKeyPemString()
require.NoError(t, err, "failed converting rsa key to pem")
importPem := pem1 + "\n" + pem2
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "keys/import",
Storage: s,
Data: map[string]interface{}{
"key_name": "my-ec-key",
"pem_bundle": importPem,
},
MountPoint: "pki/",
})
require.NoError(t, err, "got a 500 error type response from a bad pem bundle")
require.NotNil(t, resp, "Got nil response importing a bad pem bundle")
require.True(t, resp.IsError(), "should have received an error response importing a pem bundle with more than 1 key")
ctx := context.Background()
sc := b.makeStorageContext(ctx, s)
keys, _ := sc.listKeys()
for _, keyId := range keys {
id, _ := sc.fetchKeyById(keyId)
t.Logf("%s:%s", id.ID, id.Name)
}
}

View File

@ -1,742 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"testing"
"time"
"github.com/hashicorp/go-secure-stdlib/parseutil"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ocsp"
)
// If the ocsp_disabled flag is set to true in the crl configuration make sure we always
// return an Unauthorized error back as we assume an end-user disabling the feature does
// not want us to act as the OCSP authority and the RFC specifies this is the appropriate response.
func TestOcsp_Disabled(t *testing.T) {
t.Parallel()
type testArgs struct {
reqType string
}
var tests []testArgs
for _, reqType := range []string{"get", "post"} {
tests = append(tests, testArgs{
reqType: reqType,
})
}
for _, tt := range tests {
localTT := tt
t.Run(localTT.reqType, func(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "rsa")
resp, err := CBWrite(b, s, "config/crl", map[string]interface{}{
"ocsp_disable": "true",
})
requireSuccessNonNilResponse(t, resp, err)
resp, err = SendOcspRequest(t, b, s, localTT.reqType, testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
})
}
}
// If we can't find the issuer within the request and have no default issuer to sign an Unknown response
// with return an UnauthorizedErrorResponse/according to/the RFC, similar to if we are disabled (lack of authority)
// This behavior differs from CRLs when an issuer is removed from a mount.
func TestOcsp_UnknownIssuerWithNoDefault(t *testing.T) {
t.Parallel()
_, _, testEnv := setupOcspEnv(t, "ec")
// Create another completely empty mount so the created issuer/certificate above is unknown
b, s := CreateBackendWithStorage(t)
resp, err := SendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// If the issuer in the request does exist, but the request coming in associates the serial with the
// wrong issuer return an Unknown response back to the caller.
func TestOcsp_WrongIssuerInRequest(t *testing.T) {
t.Parallel()
b, s, testEnv := setupOcspEnv(t, "ec")
serial := serialFromCert(testEnv.leafCertIssuer1)
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serial,
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
resp, err = SendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer2, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Unknown, ocspResp.Status)
}
// Verify that requests we can't properly decode result in the correct response of MalformedRequestError
func TestOcsp_MalformedRequests(t *testing.T) {
t.Parallel()
type testArgs struct {
reqType string
}
var tests []testArgs
for _, reqType := range []string{"get", "post"} {
tests = append(tests, testArgs{
reqType: reqType,
})
}
for _, tt := range tests {
localTT := tt
t.Run(localTT.reqType, func(t *testing.T) {
b, s, _ := setupOcspEnv(t, "rsa")
badReq := []byte("this is a bad request")
var resp *logical.Response
var err error
switch localTT.reqType {
case "get":
resp, err = sendOcspGetRequest(b, s, badReq)
case "post":
resp, err = sendOcspPostRequest(b, s, badReq)
default:
t.Fatalf("bad request type")
}
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 400, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.MalformedRequestErrorResponse, respDer)
})
}
}
// Validate that we properly handle a revocation entry that contains an issuer ID that no longer exists,
// the best we can do in this use case is to respond back with the default issuer that we don't know
// the issuer that they are requesting (we can't guarantee that the client is actually requesting a serial
// from that issuer)
func TestOcsp_InvalidIssuerIdInRevocationEntry(t *testing.T) {
t.Parallel()
b, s, testEnv := setupOcspEnv(t, "ec")
ctx := context.Background()
// Revoke the entry
serial := serialFromCert(testEnv.leafCertIssuer1)
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serial,
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Twiddle the entry so that the issuer id is no longer valid.
storagePath := revokedPath + normalizeSerial(serial)
var revInfo revocationInfo
revEntry, err := s.Get(ctx, storagePath)
require.NoError(t, err, "failed looking up storage path: %s", storagePath)
err = revEntry.DecodeJSON(&revInfo)
require.NoError(t, err, "failed decoding storage entry: %v", revEntry)
revInfo.CertificateIssuer = "00000000-0000-0000-0000-000000000000"
revEntry, err = logical.StorageEntryJSON(storagePath, revInfo)
require.NoError(t, err, "failed re-encoding revocation info: %v", revInfo)
err = s.Put(ctx, revEntry)
require.NoError(t, err, "failed writing out new revocation entry: %v", revEntry)
// Send the request
resp, err = SendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Unknown, ocspResp.Status)
}
// Validate that we properly handle an unknown issuer use-case but that the default issuer
// does not have the OCSP usage flag set, we can't do much else other than reply with an
// Unauthorized response.
func TestOcsp_UnknownIssuerIdWithDefaultHavingOcspUsageRemoved(t *testing.T) {
t.Parallel()
b, s, testEnv := setupOcspEnv(t, "ec")
ctx := context.Background()
// Revoke the entry
serial := serialFromCert(testEnv.leafCertIssuer1)
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serial,
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("revoke"), logical.UpdateOperation), resp, true)
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Twiddle the entry so that the issuer id is no longer valid.
storagePath := revokedPath + normalizeSerial(serial)
var revInfo revocationInfo
revEntry, err := s.Get(ctx, storagePath)
require.NoError(t, err, "failed looking up storage path: %s", storagePath)
err = revEntry.DecodeJSON(&revInfo)
require.NoError(t, err, "failed decoding storage entry: %v", revEntry)
revInfo.CertificateIssuer = "00000000-0000-0000-0000-000000000000"
revEntry, err = logical.StorageEntryJSON(storagePath, revInfo)
require.NoError(t, err, "failed re-encoding revocation info: %v", revInfo)
err = s.Put(ctx, revEntry)
require.NoError(t, err, "failed writing out new revocation entry: %v", revEntry)
// Update our issuers to no longer have the OcspSigning usage
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId1.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer1")
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId2.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer2")
// Send the request
resp, err = SendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// Verify that if we do have a revoked certificate entry for the request, that matches an
// issuer but that issuer does not have the OcspUsage flag set that we return an Unauthorized
// response back to the caller
func TestOcsp_RevokedCertHasIssuerWithoutOcspUsage(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "ec")
// Revoke our certificate
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serialFromCert(testEnv.leafCertIssuer1),
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Update our issuer to no longer have the OcspSigning usage
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId1.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer")
requireFieldsSetInResp(t, resp, "usage")
// Do not assume a specific ordering for usage...
usages, err := NewIssuerUsageFromNames(strings.Split(resp.Data["usage"].(string), ","))
require.NoError(t, err, "failed parsing usage return value")
require.True(t, usages.HasUsage(IssuanceUsage))
require.True(t, usages.HasUsage(CRLSigningUsage))
require.False(t, usages.HasUsage(OCSPSigningUsage))
// Request an OCSP request from it, we should get an Unauthorized response back
resp, err = SendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// Verify if our matching issuer for a revocation entry has no key associated with it that
// we bail with an Unauthorized response.
func TestOcsp_RevokedCertHasIssuerWithoutAKey(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "ec")
// Revoke our certificate
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serialFromCert(testEnv.leafCertIssuer1),
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Delete the key associated with our issuer
resp, err = CBRead(b, s, "issuer/"+testEnv.issuerId1.String())
requireSuccessNonNilResponse(t, resp, err, "failed reading issuer")
requireFieldsSetInResp(t, resp, "key_id")
keyId := resp.Data["key_id"].(keyID)
// This is a bit naughty but allow me to delete the key...
sc := b.makeStorageContext(context.Background(), s)
issuer, err := sc.fetchIssuerById(testEnv.issuerId1)
require.NoError(t, err, "failed to get issuer from storage")
issuer.KeyID = ""
err = sc.writeIssuer(issuer)
require.NoError(t, err, "failed to write issuer update")
resp, err = CBDelete(b, s, "key/"+keyId.String())
requireSuccessNonNilResponse(t, resp, err, "failed deleting key")
// Request an OCSP request from it, we should get an Unauthorized response back
resp, err = SendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// Verify if for some reason an end-user has rotated an existing certificate using the same
// key so our algo matches multiple issuers and one has OCSP usage disabled. We expect that
// even if a prior issuer issued the certificate, the new matching issuer can respond and sign
// the response to the caller on its behalf.
//
// NOTE: This test is a bit at the mercy of iteration order of the issuer ids.
//
// If it becomes flaky, most likely something is wrong in the code
// and not the test.
func TestOcsp_MultipleMatchingIssuersOneWithoutSigningUsage(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "ec")
// Create a matching issuer as issuer1 with the same backing key
resp, err := CBWrite(b, s, "root/rotate/existing", map[string]interface{}{
"key_ref": testEnv.keyId1,
"ttl": "40h",
"common_name": "example-ocsp.com",
})
requireSuccessNonNilResponse(t, resp, err, "rotate issuer failed")
requireFieldsSetInResp(t, resp, "issuer_id")
rotatedCert := parseCert(t, resp.Data["certificate"].(string))
// Remove ocsp signing from our issuer
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId1.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer")
requireFieldsSetInResp(t, resp, "usage")
// Do not assume a specific ordering for usage...
usages, err := NewIssuerUsageFromNames(strings.Split(resp.Data["usage"].(string), ","))
require.NoError(t, err, "failed parsing usage return value")
require.True(t, usages.HasUsage(IssuanceUsage))
require.True(t, usages.HasUsage(CRLSigningUsage))
require.False(t, usages.HasUsage(OCSPSigningUsage))
// Request an OCSP request from it, we should get a Good response back, from the rotated cert
resp, err = SendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Good, ocspResp.Status)
require.Equal(t, crypto.SHA1, ocspResp.IssuerHash)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer1.SerialNumber, ocspResp.SerialNumber)
requireOcspSignatureAlgoForKey(t, rotatedCert.SignatureAlgorithm, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, rotatedCert)
}
// Make sure OCSP GET/POST requests work through the entire stack, and not just
// through the quicker backend layer the other tests are doing.
func TestOcsp_HigherLevel(t *testing.T) {
t.Parallel()
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
mountPKIEndpoint(t, client, "pki")
resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"key_type": "ec",
"common_name": "root-ca.com",
"ttl": "600h",
})
require.NoError(t, err, "error generating root ca: %v", err)
require.NotNil(t, resp, "expected ca info from root")
issuerCert := parseCert(t, resp.Data["certificate"].(string))
resp, err = client.Logical().Write("pki/roles/example", map[string]interface{}{
"allowed_domains": "example.com",
"allow_subdomains": "true",
"no_store": "false", // make sure we store this cert
"max_ttl": "1h",
"key_type": "ec",
})
require.NoError(t, err, "error setting up pki role: %v", err)
resp, err = client.Logical().Write("pki/issue/example", map[string]interface{}{
"common_name": "test.example.com",
"ttl": "15m",
})
require.NoError(t, err, "error issuing certificate: %v", err)
require.NotNil(t, resp, "got nil response from issuing request")
certToRevoke := parseCert(t, resp.Data["certificate"].(string))
serialNum := resp.Data["serial_number"].(string)
// Revoke the certificate
resp, err = client.Logical().Write("pki/revoke", map[string]interface{}{
"serial_number": serialNum,
})
require.NoError(t, err, "error revoking certificate: %v", err)
require.NotNil(t, resp, "got nil response from revoke")
// Make sure that OCSP handler responds properly
ocspReq := generateRequest(t, crypto.SHA256, certToRevoke, issuerCert)
ocspPostReq := client.NewRequest(http.MethodPost, "/v1/pki/ocsp")
ocspPostReq.Headers.Set("Content-Type", "application/ocsp-request")
ocspPostReq.BodyBytes = ocspReq
rawResp, err := client.RawRequest(ocspPostReq)
require.NoError(t, err, "failed sending ocsp post request")
require.Equal(t, 200, rawResp.StatusCode)
require.Equal(t, ocspResponseContentType, rawResp.Header.Get("Content-Type"))
bodyReader := rawResp.Body
respDer, err := io.ReadAll(bodyReader)
bodyReader.Close()
require.NoError(t, err, "failed reading response body")
ocspResp, err := ocsp.ParseResponse(respDer, issuerCert)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Revoked, ocspResp.Status)
require.Equal(t, certToRevoke.SerialNumber, ocspResp.SerialNumber)
// Test OCSP Get request for ocsp
urlEncoded := base64.StdEncoding.EncodeToString(ocspReq)
if strings.Contains(urlEncoded, "//") {
// workaround known redirect bug that is difficult to fix
t.Skipf("VAULT-13630 - Skipping GET OCSP test with encoded issuer cert containing // triggering redirection bug")
}
ocspGetReq := client.NewRequest(http.MethodGet, "/v1/pki/ocsp/"+urlEncoded)
ocspGetReq.Headers.Set("Content-Type", "application/ocsp-request")
rawResp, err = client.RawRequest(ocspGetReq)
require.NoError(t, err, "failed sending ocsp get request")
require.Equal(t, 200, rawResp.StatusCode)
require.Equal(t, ocspResponseContentType, rawResp.Header.Get("Content-Type"))
bodyReader = rawResp.Body
respDer, err = io.ReadAll(bodyReader)
bodyReader.Close()
require.NoError(t, err, "failed reading response body")
ocspResp, err = ocsp.ParseResponse(respDer, issuerCert)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Revoked, ocspResp.Status)
require.Equal(t, certToRevoke.SerialNumber, ocspResp.SerialNumber)
}
// TestOcsp_NextUpdate make sure that we are setting the appropriate values
// for the NextUpdate field within our responses.
func TestOcsp_NextUpdate(t *testing.T) {
// Within the runOcspRequestTest, with a ocspExpiry of 0,
// we will validate that NextUpdate was not set in the response
runOcspRequestTest(t, "POST", "ec", 0, 0, crypto.SHA256, 0)
// Within the runOcspRequestTest, with a ocspExpiry of 24 hours, we will validate
// that NextUpdate is set and has a time 24 hours larger than ThisUpdate
runOcspRequestTest(t, "POST", "ec", 0, 0, crypto.SHA256, 24*time.Hour)
}
func TestOcsp_ValidRequests(t *testing.T) {
type caKeyConf struct {
keyType string
keyBits int
sigBits int
}
t.Parallel()
type testArgs struct {
reqType string
keyConf caKeyConf
reqHash crypto.Hash
}
var tests []testArgs
for _, reqType := range []string{"get", "post"} {
for _, keyConf := range []caKeyConf{
{"rsa", 0, 0},
{"rsa", 0, 384},
{"rsa", 0, 512},
{"ec", 0, 0},
{"ec", 521, 0},
} {
// "ed25519" is not supported at the moment in x/crypto/ocsp
for _, requestHash := range []crypto.Hash{crypto.SHA1, crypto.SHA256, crypto.SHA384, crypto.SHA512} {
tests = append(tests, testArgs{
reqType: reqType,
keyConf: keyConf,
reqHash: requestHash,
})
}
}
}
for _, tt := range tests {
localTT := tt
testName := fmt.Sprintf("%s-%s-keybits-%d-sigbits-%d-reqHash-%s", localTT.reqType, localTT.keyConf.keyType,
localTT.keyConf.keyBits,
localTT.keyConf.sigBits,
localTT.reqHash)
t.Run(testName, func(t *testing.T) {
runOcspRequestTest(t, localTT.reqType, localTT.keyConf.keyType, localTT.keyConf.keyBits,
localTT.keyConf.sigBits, localTT.reqHash, 12*time.Hour)
})
}
}
func runOcspRequestTest(t *testing.T, requestType string, caKeyType string,
caKeyBits int, caKeySigBits int, requestHash crypto.Hash, ocspExpiry time.Duration,
) {
b, s, testEnv := setupOcspEnvWithCaKeyConfig(t, caKeyType, caKeyBits, caKeySigBits, ocspExpiry)
// Non-revoked cert
resp, err := SendOcspRequest(t, b, s, requestType, testEnv.leafCertIssuer1, testEnv.issuer1, requestHash)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Good, ocspResp.Status)
require.Equal(t, requestHash, ocspResp.IssuerHash)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer1.SerialNumber, ocspResp.SerialNumber)
requireOcspSignatureAlgoForKey(t, testEnv.issuer1.SignatureAlgorithm, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, testEnv.issuer1)
// Now revoke it
resp, err = CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serialFromCert(testEnv.leafCertIssuer1),
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
resp, err = SendOcspRequest(t, b, s, requestType, testEnv.leafCertIssuer1, testEnv.issuer1, requestHash)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request with revoked")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer = resp.Data["http_raw_body"].([]byte)
ocspResp, err = ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response with revoked")
require.Equal(t, ocsp.Revoked, ocspResp.Status)
require.Equal(t, requestHash, ocspResp.IssuerHash)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer1.SerialNumber, ocspResp.SerialNumber)
requireOcspSignatureAlgoForKey(t, testEnv.issuer1.SignatureAlgorithm, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, testEnv.issuer1)
// Request status for our second issuer
resp, err = SendOcspRequest(t, b, s, requestType, testEnv.leafCertIssuer2, testEnv.issuer2, requestHash)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer = resp.Data["http_raw_body"].([]byte)
ocspResp, err = ocsp.ParseResponse(respDer, testEnv.issuer2)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Good, ocspResp.Status)
require.Equal(t, requestHash, ocspResp.IssuerHash)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer2.SerialNumber, ocspResp.SerialNumber)
// Verify that our thisUpdate and nextUpdate fields are updated as expected
resp, err = CBRead(b, s, "config/crl")
requireSuccessNonNilResponse(t, resp, err, "failed reading from config/crl")
requireFieldsSetInResp(t, resp, "ocsp_expiry")
ocspExpiryRaw := resp.Data["ocsp_expiry"].(string)
expectedDiff, err := parseutil.ParseDurationSecond(ocspExpiryRaw)
require.NoError(t, err, "failed to parse default ocsp expiry value")
thisUpdate := ocspResp.ThisUpdate
require.Less(t, time.Since(thisUpdate), 10*time.Second, "expected ThisUpdate field to be within the last 10 seconds")
if expectedDiff != 0 {
nextUpdate := ocspResp.NextUpdate
require.False(t, nextUpdate.IsZero(), "nextUpdate field value should have been a non-zero time")
require.True(t, thisUpdate.Before(nextUpdate),
fmt.Sprintf("thisUpdate %s, should have been before nextUpdate: %s", thisUpdate, nextUpdate))
nextUpdateDiff := nextUpdate.Sub(thisUpdate)
require.Equal(t, expectedDiff, nextUpdateDiff,
fmt.Sprintf("the delta between thisUpdate %s and nextUpdate: %s should have been around: %s but was %s",
thisUpdate, nextUpdate, defaultCrlConfig.OcspExpiry, nextUpdateDiff))
} else {
// With the config value set to 0, we shouldn't have a NextUpdate field set
require.True(t, ocspResp.NextUpdate.IsZero(), "nextUpdate value was not zero as expected was: %v", ocspResp.NextUpdate)
}
requireOcspSignatureAlgoForKey(t, testEnv.issuer2.SignatureAlgorithm, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, testEnv.issuer2)
}
func requireOcspSignatureAlgoForKey(t *testing.T, expected x509.SignatureAlgorithm, actual x509.SignatureAlgorithm) {
t.Helper()
require.Equal(t, expected.String(), actual.String())
}
type ocspTestEnv struct {
issuer1 *x509.Certificate
issuer2 *x509.Certificate
issuerId1 issuerID
issuerId2 issuerID
leafCertIssuer1 *x509.Certificate
leafCertIssuer2 *x509.Certificate
keyId1 keyID
keyId2 keyID
}
func setupOcspEnv(t *testing.T, keyType string) (*backend, logical.Storage, *ocspTestEnv) {
return setupOcspEnvWithCaKeyConfig(t, keyType, 0, 0, 12*time.Hour)
}
func setupOcspEnvWithCaKeyConfig(t *testing.T, keyType string, caKeyBits int, caKeySigBits int, ocspExpiry time.Duration) (*backend, logical.Storage, *ocspTestEnv) {
b, s := CreateBackendWithStorage(t)
var issuerCerts []*x509.Certificate
var leafCerts []*x509.Certificate
var issuerIds []issuerID
var keyIds []keyID
resp, err := CBWrite(b, s, "config/crl", map[string]interface{}{
"ocsp_enable": true,
"ocsp_expiry": fmt.Sprintf("%ds", int(ocspExpiry.Seconds())),
})
requireSuccessNonNilResponse(t, resp, err, "config/crl failed")
for i := 0; i < 2; i++ {
resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"key_type": keyType,
"key_bits": caKeyBits,
"signature_bits": caKeySigBits,
"ttl": "40h",
"common_name": "example-ocsp.com",
})
requireSuccessNonNilResponse(t, resp, err, "root/generate/internal")
requireFieldsSetInResp(t, resp, "issuer_id", "key_id")
issuerId := resp.Data["issuer_id"].(issuerID)
keyId := resp.Data["key_id"].(keyID)
resp, err = CBWrite(b, s, "roles/test"+strconv.FormatInt(int64(i), 10), map[string]interface{}{
"allow_bare_domains": true,
"allow_subdomains": true,
"allowed_domains": "foobar.com",
"no_store": false,
"generate_lease": false,
"issuer_ref": issuerId,
"key_type": keyType,
})
requireSuccessNonNilResponse(t, resp, err, "roles/test"+strconv.FormatInt(int64(i), 10))
resp, err = CBWrite(b, s, "issue/test"+strconv.FormatInt(int64(i), 10), map[string]interface{}{
"common_name": "test.foobar.com",
})
requireSuccessNonNilResponse(t, resp, err, "roles/test"+strconv.FormatInt(int64(i), 10))
requireFieldsSetInResp(t, resp, "certificate", "issuing_ca", "serial_number")
leafCert := parseCert(t, resp.Data["certificate"].(string))
issuingCa := parseCert(t, resp.Data["issuing_ca"].(string))
issuerCerts = append(issuerCerts, issuingCa)
leafCerts = append(leafCerts, leafCert)
issuerIds = append(issuerIds, issuerId)
keyIds = append(keyIds, keyId)
}
testEnv := &ocspTestEnv{
issuerId1: issuerIds[0],
issuer1: issuerCerts[0],
leafCertIssuer1: leafCerts[0],
keyId1: keyIds[0],
issuerId2: issuerIds[1],
issuer2: issuerCerts[1],
leafCertIssuer2: leafCerts[1],
keyId2: keyIds[1],
}
return b, s, testEnv
}
func SendOcspRequest(t *testing.T, b *backend, s logical.Storage, getOrPost string, cert, issuer *x509.Certificate, requestHash crypto.Hash) (*logical.Response, error) {
t.Helper()
ocspRequest := generateRequest(t, requestHash, cert, issuer)
switch strings.ToLower(getOrPost) {
case "get":
return sendOcspGetRequest(b, s, ocspRequest)
case "post":
return sendOcspPostRequest(b, s, ocspRequest)
default:
t.Fatalf("unsupported value for SendOcspRequest getOrPost arg: %s", getOrPost)
}
return nil, nil
}
func sendOcspGetRequest(b *backend, s logical.Storage, ocspRequest []byte) (*logical.Response, error) {
urlEncoded := base64.StdEncoding.EncodeToString(ocspRequest)
return CBRead(b, s, "ocsp/"+urlEncoded)
}
func sendOcspPostRequest(b *backend, s logical.Storage, ocspRequest []byte) (*logical.Response, error) {
reader := io.NopCloser(bytes.NewReader(ocspRequest))
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "ocsp",
Storage: s,
MountPoint: "pki/",
HTTPRequest: &http.Request{
Body: reader,
},
})
return resp, err
}

View File

@ -1,512 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"fmt"
"math/big"
"testing"
"time"
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
"github.com/hashicorp/vault/api"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/vault"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
func TestResignCrls_ForbidSigningOtherIssuerCRL(t *testing.T) {
t.Parallel()
// Some random CRL from another issuer
pem1 := "-----BEGIN X509 CRL-----\nMIIBvjCBpwIBATANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDExByb290LWV4YW1w\nbGUuY29tFw0yMjEwMjYyMTI5MzlaFw0yMjEwMjkyMTI5MzlaMCcwJQIUSnVf8wsd\nHjOt9drCYFhWxS9QqGoXDTIyMTAyNjIxMjkzOVqgLzAtMB8GA1UdIwQYMBaAFHki\nZ0XDUQVSajNRGXrg66OaIFlYMAoGA1UdFAQDAgEDMA0GCSqGSIb3DQEBCwUAA4IB\nAQBGIdtqTwemnLZF5AoP+jzvKZ26S3y7qvRIzd7f4A0EawzYmWXSXfwqo4TQ4DG3\nnvT+AaA1zCCOlH/1U+ufN9gSSN0j9ax58brSYMnMskMCqhLKIp0qnvS4jr/gopmF\nv8grbvLHEqNYTu1T7umMLdNQUsWT3Qc+EIjfoKj8xD2FHsZwJ+EMbytwl8Unipjr\nhz4rmcES/65vavfdFpOI6YXfi+UAaHBdkTqmHgg4BdpuXfYtlf+iotFSOkygD5fl\n0D+RVFW9uJv2WfbQ7kRt1X/VcFk/onw0AQqxZRVUzvjoMw+EMcxSq3UKOlXcWDxm\nEFz9rFQQ66L388EP8RD7Dh3X\n-----END X509 CRL-----"
b, s := CreateBackendWithStorage(t)
resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "test.com",
"key_type": "ec",
})
requireSuccessNonNilResponse(t, resp, err)
resp, err = CBWrite(b, s, "issuer/default/resign-crls", map[string]interface{}{
"crl_number": "2",
"next_update": "1h",
"format": "pem",
"crls": []string{pem1},
})
require.ErrorContains(t, err, "was not signed by requested issuer")
}
func TestResignCrls_NormalCrl(t *testing.T) {
t.Parallel()
b1, s1 := CreateBackendWithStorage(t)
b2, s2 := CreateBackendWithStorage(t)
// Setup two backends, with the same key material/certificate with a different leaf in each that is revoked.
caCert, serial1, serial2, crl1, crl2 := setupResignCrlMounts(t, b1, s1, b2, s2)
// Attempt to combine the CRLs
resp, err := CBWrite(b1, s1, "issuer/default/resign-crls", map[string]interface{}{
"crl_number": "2",
"next_update": "1h",
"format": "pem",
"crls": []string{crl1, crl2},
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b1.Route("issuer/default/resign-crls"), logical.UpdateOperation), resp, true)
requireSuccessNonNilResponse(t, resp, err)
requireFieldsSetInResp(t, resp, "crl")
pemCrl := resp.Data["crl"].(string)
combinedCrl, err := decodePemCrl(pemCrl)
require.NoError(t, err, "failed decoding combined CRL")
serials := extractSerialsFromCrl(t, combinedCrl)
require.Contains(t, serials, serial1)
require.Contains(t, serials, serial2)
require.Equal(t, 2, len(serials), "serials contained more serials than expected")
require.Equal(t, big.NewInt(int64(2)), combinedCrl.Number)
require.Equal(t, combinedCrl.ThisUpdate.Add(1*time.Hour), combinedCrl.NextUpdate)
extensions := combinedCrl.Extensions
requireExtensionOid(t, []int{2, 5, 29, 20}, extensions) // CRL Number Extension
requireExtensionOid(t, []int{2, 5, 29, 35}, extensions) // akidOid
require.Equal(t, 2, len(extensions))
err = combinedCrl.CheckSignatureFrom(caCert)
require.NoError(t, err, "failed signature check of CRL")
}
func TestResignCrls_EliminateDuplicates(t *testing.T) {
t.Parallel()
b1, s1 := CreateBackendWithStorage(t)
b2, s2 := CreateBackendWithStorage(t)
// Setup two backends, with the same key material/certificate with a different leaf in each that is revoked.
_, serial1, _, crl1, _ := setupResignCrlMounts(t, b1, s1, b2, s2)
// Pass in the same CRLs to make sure we do not duplicate entries
resp, err := CBWrite(b1, s1, "issuer/default/resign-crls", map[string]interface{}{
"crl_number": "2",
"next_update": "1h",
"format": "pem",
"crls": []string{crl1, crl1},
})
requireSuccessNonNilResponse(t, resp, err)
requireFieldsSetInResp(t, resp, "crl")
pemCrl := resp.Data["crl"].(string)
combinedCrl, err := decodePemCrl(pemCrl)
require.NoError(t, err, "failed decoding combined CRL")
// Technically this will die if we have duplicates.
serials := extractSerialsFromCrl(t, combinedCrl)
// We should have no warnings about collisions if they have the same revoked time
require.Empty(t, resp.Warnings, "expected no warnings in response")
require.Contains(t, serials, serial1)
require.Equal(t, 1, len(serials), "serials contained more serials than expected")
}
func TestResignCrls_ConflictingExpiry(t *testing.T) {
t.Parallel()
b1, s1 := CreateBackendWithStorage(t)
b2, s2 := CreateBackendWithStorage(t)
// Setup two backends, with the same key material/certificate with a different leaf in each that is revoked.
_, serial1, serial2, crl1, _ := setupResignCrlMounts(t, b1, s1, b2, s2)
timeAfterMountSetup := time.Now()
// Read in serial1 from mount 1
resp, err := CBRead(b1, s1, "cert/"+serial1)
requireSuccessNonNilResponse(t, resp, err, "failed reading serial 1's certificate")
requireFieldsSetInResp(t, resp, "certificate")
cert1 := resp.Data["certificate"].(string)
// Wait until at least we have rolled over to the next second to match sure the generated CRL time
// on backend 2 for the serial 1 will be different
for {
if time.Now().After(timeAfterMountSetup.Add(1 * time.Second)) {
break
}
}
// Use BYOC to revoke the same certificate on backend 2 now
resp, err = CBWrite(b2, s2, "revoke", map[string]interface{}{
"certificate": cert1,
})
requireSuccessNonNilResponse(t, resp, err, "failed revoking serial 1 on backend 2")
// Fetch the new CRL from backend2 now
resp, err = CBRead(b2, s2, "cert/crl")
requireSuccessNonNilResponse(t, resp, err, "error fetch crl from backend 2")
requireFieldsSetInResp(t, resp, "certificate")
crl2 := resp.Data["certificate"].(string)
// Attempt to combine the CRLs
resp, err = CBWrite(b1, s1, "issuer/default/resign-crls", map[string]interface{}{
"crl_number": "2",
"next_update": "1h",
"format": "pem",
"crls": []string{crl2, crl1}, // Make sure we don't just grab the first colliding entry...
})
requireSuccessNonNilResponse(t, resp, err)
requireFieldsSetInResp(t, resp, "crl")
pemCrl := resp.Data["crl"].(string)
combinedCrl, err := decodePemCrl(pemCrl)
require.NoError(t, err, "failed decoding combined CRL")
combinedSerials := extractSerialsFromCrl(t, combinedCrl)
require.Contains(t, combinedSerials, serial1)
require.Contains(t, combinedSerials, serial2)
require.Equal(t, 2, len(combinedSerials), "serials contained more serials than expected")
// Make sure we issued a warning about the time collision
require.NotEmpty(t, resp.Warnings, "expected at least one warning")
require.Contains(t, resp.Warnings[0], "different revocation times detected")
// Make sure we have the initial revocation time from backend 1 within the combined CRL.
decodedCrl1, err := decodePemCrl(crl1)
require.NoError(t, err, "failed decoding crl from backend 1")
serialsFromBackend1 := extractSerialsFromCrl(t, decodedCrl1)
require.Equal(t, serialsFromBackend1[serial1], combinedSerials[serial1])
// Make sure we have the initial revocation time from backend 1 does not match with backend 2's time
decodedCrl2, err := decodePemCrl(crl2)
require.NoError(t, err, "failed decoding crl from backend 2")
serialsFromBackend2 := extractSerialsFromCrl(t, decodedCrl2)
require.NotEqual(t, serialsFromBackend1[serial1], serialsFromBackend2[serial1])
}
func TestResignCrls_DeltaCrl(t *testing.T) {
t.Parallel()
b1, s1 := CreateBackendWithStorage(t)
b2, s2 := CreateBackendWithStorage(t)
// Setup two backends, with the same key material/certificate with a different leaf in each that is revoked.
caCert, serial1, serial2, crl1, crl2 := setupResignCrlMounts(t, b1, s1, b2, s2)
resp, err := CBWrite(b1, s1, "issuer/default/resign-crls", map[string]interface{}{
"crl_number": "5",
"delta_crl_base_number": "4",
"next_update": "12h",
"format": "pem",
"crls": []string{crl1, crl2},
})
requireSuccessNonNilResponse(t, resp, err)
requireFieldsSetInResp(t, resp, "crl")
pemCrl := resp.Data["crl"].(string)
combinedCrl, err := decodePemCrl(pemCrl)
require.NoError(t, err, "failed decoding combined CRL")
serials := extractSerialsFromCrl(t, combinedCrl)
require.Contains(t, serials, serial1)
require.Contains(t, serials, serial2)
require.Equal(t, 2, len(serials), "serials contained more serials than expected")
require.Equal(t, big.NewInt(int64(5)), combinedCrl.Number)
require.Equal(t, combinedCrl.ThisUpdate.Add(12*time.Hour), combinedCrl.NextUpdate)
extensions := combinedCrl.Extensions
requireExtensionOid(t, []int{2, 5, 29, 27}, extensions) // Delta CRL Extension
requireExtensionOid(t, []int{2, 5, 29, 20}, extensions) // CRL Number Extension
requireExtensionOid(t, []int{2, 5, 29, 35}, extensions) // akidOid
require.Equal(t, 3, len(extensions))
err = combinedCrl.CheckSignatureFrom(caCert)
require.NoError(t, err, "failed signature check of CRL")
}
func TestSignRevocationList(t *testing.T) {
t.Parallel()
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
// Mount PKI, use this form of backend so our request is closer to reality (json parsed)
err := client.Sys().Mount("pki", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
})
require.NoError(t, err)
// Generate internal CA.
resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"ttl": "40h",
"common_name": "myvault.com",
})
require.NoError(t, err)
caCert := parseCert(t, resp.Data["certificate"].(string))
resp, err = client.Logical().Write("pki/issuer/default/sign-revocation-list", map[string]interface{}{
"crl_number": "1",
"next_update": "12h",
"format": "pem",
"revoked_certs": []map[string]interface{}{
{
"serial_number": "37:60:16:e4:85:d5:96:38:3a:ed:31:06:8d:ed:7a:46:d4:22:63:d8",
"revocation_time": "1668614976",
"extensions": []map[string]interface{}{},
},
{
"serial_number": "27:03:89:76:5a:d4:d8:19:48:47:ca:96:db:6f:27:86:31:92:9f:82",
"revocation_time": "2022-11-16T16:09:36.739592Z",
},
{
"serial_number": "27:03:89:76:5a:d4:d8:19:48:47:ca:96:db:6f:27:86:31:92:9f:81",
"revocation_time": "2022-10-16T16:09:36.739592Z",
"extensions": []map[string]interface{}{
{
"id": "2.5.29.100",
"critical": "true",
"value": "aGVsbG8=", // "hello" base64 encoded
},
{
"id": "2.5.29.101",
"critical": "false",
"value": "Ynll", // "bye" base64 encoded
},
},
},
},
"extensions": []map[string]interface{}{
{
"id": "2.5.29.200",
"critical": "true",
"value": "aGVsbG8=", // "hello" base64 encoded
},
{
"id": "2.5.29.201",
"critical": "false",
"value": "Ynll", // "bye" base64 encoded
},
},
})
require.NoError(t, err)
pemCrl := resp.Data["crl"].(string)
crl, err := decodePemCrl(pemCrl)
require.NoError(t, err, "failed decoding CRL")
serials := extractSerialsFromCrl(t, crl)
require.Contains(t, serials, "37:60:16:e4:85:d5:96:38:3a:ed:31:06:8d:ed:7a:46:d4:22:63:d8")
require.Contains(t, serials, "27:03:89:76:5a:d4:d8:19:48:47:ca:96:db:6f:27:86:31:92:9f:82")
require.Contains(t, serials, "27:03:89:76:5a:d4:d8:19:48:47:ca:96:db:6f:27:86:31:92:9f:81")
require.Equal(t, 3, len(serials), "expected 3 serials within CRL")
// Make sure extensions on serials match what we expect.
require.Equal(t, 0, len(crl.RevokedCertificates[0].Extensions), "Expected no extensions on 1st serial")
require.Equal(t, 0, len(crl.RevokedCertificates[1].Extensions), "Expected no extensions on 2nd serial")
require.Equal(t, 2, len(crl.RevokedCertificates[2].Extensions), "Expected 2 extensions on 3 serial")
require.Equal(t, "2.5.29.100", crl.RevokedCertificates[2].Extensions[0].Id.String())
require.True(t, crl.RevokedCertificates[2].Extensions[0].Critical)
require.Equal(t, []byte("hello"), crl.RevokedCertificates[2].Extensions[0].Value)
require.Equal(t, "2.5.29.101", crl.RevokedCertificates[2].Extensions[1].Id.String())
require.False(t, crl.RevokedCertificates[2].Extensions[1].Critical)
require.Equal(t, []byte("bye"), crl.RevokedCertificates[2].Extensions[1].Value)
// CRL Number and times
require.Equal(t, big.NewInt(int64(1)), crl.Number)
require.Equal(t, crl.ThisUpdate.Add(12*time.Hour), crl.NextUpdate)
// Verify top level extensions are present
extensions := crl.Extensions
requireExtensionOid(t, []int{2, 5, 29, 20}, extensions) // CRL Number Extension
requireExtensionOid(t, []int{2, 5, 29, 35}, extensions) // akidOid
requireExtensionOid(t, []int{2, 5, 29, 200}, extensions) // Added value from param
requireExtensionOid(t, []int{2, 5, 29, 201}, extensions) // Added value from param
require.Equal(t, 4, len(extensions))
// Signature
err = crl.CheckSignatureFrom(caCert)
require.NoError(t, err, "failed signature check of CRL")
}
func TestSignRevocationList_NoRevokedCerts(t *testing.T) {
t.Parallel()
b, s := CreateBackendWithStorage(t)
resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "test.com",
})
requireSuccessNonNilResponse(t, resp, err)
resp, err = CBWrite(b, s, "issuer/default/sign-revocation-list", map[string]interface{}{
"crl_number": "10000",
"next_update": "12h",
"format": "pem",
})
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("issuer/default/sign-revocation-list"), logical.UpdateOperation), resp, true)
requireSuccessNonNilResponse(t, resp, err)
requireFieldsSetInResp(t, resp, "crl")
pemCrl := resp.Data["crl"].(string)
crl, err := decodePemCrl(pemCrl)
require.NoError(t, err, "failed decoding CRL")
serials := extractSerialsFromCrl(t, crl)
require.Equal(t, 0, len(serials), "no serials were expected in CRL")
require.Equal(t, big.NewInt(int64(10000)), crl.Number)
require.Equal(t, crl.ThisUpdate.Add(12*time.Hour), crl.NextUpdate)
}
func TestSignRevocationList_ReservedExtensions(t *testing.T) {
t.Parallel()
reservedOids := []asn1.ObjectIdentifier{
akOid, deltaCrlOid, crlNumOid,
}
// Validate there isn't copy/paste issues with our constants...
require.Equal(t, asn1.ObjectIdentifier{2, 5, 29, 27}, deltaCrlOid) // Delta CRL Extension
require.Equal(t, asn1.ObjectIdentifier{2, 5, 29, 20}, crlNumOid) // CRL Number Extension
require.Equal(t, asn1.ObjectIdentifier{2, 5, 29, 35}, akOid) // akidOid
for _, reservedOid := range reservedOids {
t.Run(reservedOid.String(), func(t *testing.T) {
b, s := CreateBackendWithStorage(t)
resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "test.com",
})
requireSuccessNonNilResponse(t, resp, err)
resp, err = CBWrite(b, s, "issuer/default/sign-revocation-list", map[string]interface{}{
"crl_number": "1",
"next_update": "12h",
"format": "pem",
"extensions": []map[string]interface{}{
{
"id": reservedOid.String(),
"critical": "false",
"value": base64.StdEncoding.EncodeToString([]byte("hello")),
},
},
})
require.ErrorContains(t, err, "is reserved")
})
}
}
func setupResignCrlMounts(t *testing.T, b1 *backend, s1 logical.Storage, b2 *backend, s2 logical.Storage) (*x509.Certificate, string, string, string, string) {
t.Helper()
// Setup two mounts with the same CA/key material
resp, err := CBWrite(b1, s1, "root/generate/exported", map[string]interface{}{
"common_name": "test.com",
})
requireSuccessNonNilResponse(t, resp, err)
requireFieldsSetInResp(t, resp, "certificate", "private_key")
pemCaCert := resp.Data["certificate"].(string)
caCert := parseCert(t, pemCaCert)
privKey := resp.Data["private_key"].(string)
// Import the above key/cert into another mount
resp, err = CBWrite(b2, s2, "config/ca", map[string]interface{}{
"pem_bundle": pemCaCert + "\n" + privKey,
})
requireSuccessNonNilResponse(t, resp, err, "error setting up CA on backend 2")
// Create the same role in both mounts
resp, err = CBWrite(b1, s1, "roles/test", map[string]interface{}{
"allowed_domains": "test.com",
"allow_subdomains": "true",
"max_ttl": "1h",
})
requireSuccessNonNilResponse(t, resp, err, "error setting up pki role on backend 1")
resp, err = CBWrite(b2, s2, "roles/test", map[string]interface{}{
"allowed_domains": "test.com",
"allow_subdomains": "true",
"max_ttl": "1h",
})
requireSuccessNonNilResponse(t, resp, err, "error setting up pki role on backend 2")
// Issue and revoke a cert in backend 1
resp, err = CBWrite(b1, s1, "issue/test", map[string]interface{}{
"common_name": "test1.test.com",
})
requireSuccessNonNilResponse(t, resp, err, "error issuing cert from backend 1")
requireFieldsSetInResp(t, resp, "serial_number")
serial1 := resp.Data["serial_number"].(string)
resp, err = CBWrite(b1, s1, "revoke", map[string]interface{}{"serial_number": serial1})
requireSuccessNonNilResponse(t, resp, err, "error revoking cert from backend 2")
// Issue and revoke a cert in backend 2
resp, err = CBWrite(b2, s2, "issue/test", map[string]interface{}{
"common_name": "test1.test.com",
})
requireSuccessNonNilResponse(t, resp, err, "error issuing cert from backend 2")
requireFieldsSetInResp(t, resp, "serial_number")
serial2 := resp.Data["serial_number"].(string)
resp, err = CBWrite(b2, s2, "revoke", map[string]interface{}{"serial_number": serial2})
requireSuccessNonNilResponse(t, resp, err, "error revoking cert from backend 2")
// Fetch PEM CRLs from each
resp, err = CBRead(b1, s1, "cert/crl")
requireSuccessNonNilResponse(t, resp, err, "error fetch crl from backend 1")
requireFieldsSetInResp(t, resp, "certificate")
crl1 := resp.Data["certificate"].(string)
resp, err = CBRead(b2, s2, "cert/crl")
requireSuccessNonNilResponse(t, resp, err, "error fetch crl from backend 2")
requireFieldsSetInResp(t, resp, "certificate")
crl2 := resp.Data["certificate"].(string)
return caCert, serial1, serial2, crl1, crl2
}
func requireExtensionOid(t *testing.T, identifier asn1.ObjectIdentifier, extensions []pkix.Extension, msgAndArgs ...interface{}) {
t.Helper()
found := false
var oidsInExtensions []string
for _, extension := range extensions {
oidsInExtensions = append(oidsInExtensions, extension.Id.String())
if extension.Id.Equal(identifier) {
found = true
break
}
}
if !found {
msg := fmt.Sprintf("Failed to find matching asn oid %s out of %v", identifier.String(), oidsInExtensions)
require.Fail(t, msg, msgAndArgs)
}
}
func extractSerialsFromCrl(t *testing.T, crl *x509.RevocationList) map[string]time.Time {
t.Helper()
serials := map[string]time.Time{}
for _, revokedCert := range crl.RevokedCertificates {
serial := serialFromBigInt(revokedCert.SerialNumber)
if _, exists := serials[serial]; exists {
t.Fatalf("Serial number %s was duplicated in CRL", serial)
}
serials[serial] = revokedCert.RevocationTime
}
return serials
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More