almost completely remove testing code
things gone wild
This commit is contained in:
parent
dda95e4c57
commit
c3f569436c
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
131
api/auth_test.go
131
api/auth_test.go
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
1438
api/client_test.go
1438
api/client_test.go
File diff suppressed because it is too large
Load Diff
390
api/kv_test.go
390
api/kv_test.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}`
|
|
@ -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 = `{}`
|
|
@ -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"
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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"}
|
||||
`
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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(), ":"))
|
||||
}
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
|
@ -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-----
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -1 +0,0 @@
|
|||
92223EAFBBEE17AF
|
|
@ -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-----
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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}}";
|
||||
`
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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-----`
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue
Block a user