// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package static import ( "context" "encoding/json" "testing" "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/kms" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/structpb" ) const ( TestSshPrivateKeyPem = ` -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQAAAJDmpbfr5qW3 6wAAAAtzc2gtZWQyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQ AAAEBvvkQkH06ad2GpX1VVARzu9NkHA6gzamAaQ/hkn5FuZvF/CEQBkquextDE6NUDc8WI Hdtbe1Kk0rHxN0yIKqXNAAAACWplZmZAYXJjaAECAwQ= -----END OPENSSH PRIVATE KEY----- ` TestLargeSshPrivateKeyPem = ` -----BEGIN OPENSSH PRIVATE KEY----- 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 -----END OPENSSH PRIVATE KEY----- ` ) // TestJsonObject returns a json object and it's marshalled format to be used for testing func TestJsonObject(t testing.TB) (credential.JsonObject, []byte) { object := credential.JsonObject{ Struct: &structpb.Struct{ Fields: map[string]*structpb.Value{ "username": structpb.NewStringValue("user"), "password": structpb.NewStringValue("password"), "hash": structpb.NewStringValue("1234567890"), }, }, } b, err := json.Marshal(object.AsMap()) require.NoError(t, err) return object, b } // TestCredentialStore creates a static credential store in the provided DB with // the provided project id and any values passed in through the Options vars. // If any errors are encountered during the creation of the store, the test will fail. func TestCredentialStore(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, projectId string, opts ...Option) *CredentialStore { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) w := db.New(conn) databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) cs, err := NewCredentialStore(projectId, opts...) assert.NoError(t, err) require.NotNil(t, cs) opt := getOpts(opts...) id := opt.withPublicId if id == "" { id, err = newCredentialStoreId(ctx) assert.NoError(t, err) require.NotEmpty(t, id) } cs.PublicId = id _, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, iw db.Writer) error { require.NoError(t, iw.Create(ctx, cs)) return nil }, ) require.NoError(t, err2) return cs } // TestCredentialStores creates count number of static credential stores in // the provided DB with the provided project id. If any errors are // encountered during the creation of the credential stores, the test will // fail. func TestCredentialStores(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, projectId string, count int) []*CredentialStore { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) css := make([]*CredentialStore, 0, count) for i := 0; i < count; i++ { css = append(css, TestCredentialStore(t, conn, wrapper, projectId)) } return css } // TestUsernamePasswordCredential creates a username password credential in the provided DB with // the provided project id and any values passed in through. // If any errors are encountered during the creation of the store, the test will fail. func TestUsernamePasswordCredential( t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, username, password, storeId, projectId string, opts ...Option, ) *UsernamePasswordCredential { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) w := db.New(conn) opt := getOpts(opts...) databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) cred, err := NewUsernamePasswordCredential(storeId, username, credential.Password(password), opts...) require.NoError(t, err) require.NotNil(t, cred) id := opt.withPublicId if id == "" { id, err = credential.NewUsernamePasswordCredentialId(ctx) require.NoError(t, err) } cred.PublicId = id err = cred.encrypt(ctx, databaseWrapper) require.NoError(t, err) _, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, iw db.Writer) error { require.NoError(t, iw.Create(ctx, cred)) return nil }, ) require.NoError(t, err2) return cred } // TestUsernamePasswordCredentials creates count number of username password credentials in // the provided DB with the provided project id. If any errors are // encountered during the creation of the credentials, the test will fail. func TestUsernamePasswordCredentials( t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, username, password, storeId, projectId string, count int, ) []*UsernamePasswordCredential { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) creds := make([]*UsernamePasswordCredential, 0, count) for i := 0; i < count; i++ { creds = append(creds, TestUsernamePasswordCredential(t, conn, wrapper, username, password, storeId, projectId)) } return creds } // TestSshPrivateKeyCredential creates an ssh private key credential in the // provided DB with the provided project and any values passed in through. If any // errors are encountered during the creation of the store, the test will fail. func TestSshPrivateKeyCredential( t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, username, privateKey, storeId, projectId string, opt ...Option, ) *SshPrivateKeyCredential { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) w := db.New(conn) opts := getOpts(opt...) databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) cred, err := NewSshPrivateKeyCredential(ctx, storeId, username, credential.PrivateKey(privateKey), opt...) require.NoError(t, err) require.NotNil(t, cred) id := opts.withPublicId if id == "" { id, err = credential.NewSshPrivateKeyCredentialId(ctx) require.NoError(t, err) } cred.PublicId = id err = cred.encrypt(ctx, databaseWrapper) require.NoError(t, err) _, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, iw db.Writer) error { require.NoError(t, iw.Create(ctx, cred)) return nil }, ) require.NoError(t, err2) return cred } // TestSshPrivateKeyCredentials creates count number of ssh private key // credentials in the provided DB with the provided project id. If any errors are // encountered during the creation of the credentials, the test will fail. func TestSshPrivateKeyCredentials( t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, username, privateKey, storeId, projectId string, count int, ) []*SshPrivateKeyCredential { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) creds := make([]*SshPrivateKeyCredential, 0, count) for i := 0; i < count; i++ { creds = append(creds, TestSshPrivateKeyCredential(t, conn, wrapper, username, privateKey, storeId, projectId)) } return creds } // TestJsonCredential creates a json credential in the // provided DB with the provided scope and any values passed in. If any // errors are encountered during the creation of the store, the test will fail. func TestJsonCredential( t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, storeId, scopeId string, object credential.JsonObject, opt ...Option, ) *JsonCredential { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) w := db.New(conn) opts := getOpts(opt...) databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) cred, err := NewJsonCredential(ctx, storeId, object, opt...) require.NoError(t, err) require.NotNil(t, cred) id := opts.withPublicId if id == "" { id, err = credential.NewJsonCredentialId(ctx) require.NoError(t, err) } cred.PublicId = id err = cred.encrypt(ctx, databaseWrapper) require.NoError(t, err) _, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, iw db.Writer) error { require.NoError(t, iw.Create(ctx, cred)) return nil }, ) require.NoError(t, err2) return cred } // TestJsonCredentials creates count number of json // credentials in the provided DB with the provided scope id. If any errors are // encountered during the creation of the credentials, the test will fail. func TestJsonCredentials( t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, storeId, scopeId string, object credential.JsonObject, count int, ) []*JsonCredential { t.Helper() ctx := context.Background() kmsCache := kms.TestKms(t, conn, wrapper) databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) assert.NoError(t, err) require.NotNil(t, databaseWrapper) creds := make([]*JsonCredential, 0, count) for i := 0; i < count; i++ { creds = append(creds, TestJsonCredential(t, conn, wrapper, storeId, scopeId, object)) } return creds }