feat(vault): Support Issuing SSH Cert Credentials
Allows the new credential library type to be pulled from the db and used for issuing ssh certificate type credentials. The ssh cert library interfaces with vault's ssh secrets engine to either issu certificates directly from vault, or to generate them within boundary and have vault sign them as a certificate authority for authentication. See: https://developer.hashicorp.com/vault/api-docs/secret/ssh#sign-ssh-key https://developer.hashicorp.com/vault/api-docs/secret/ssh#generate-certificate-and-key
This commit is contained in:
1 parent
823b8cdfc6
commit
3a78a74571
9 files changed
+865
-55
No files matched your search
@@ -161,6 +161,7 @@ require (
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
|
||||
@@ -980,6 +980,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw=
|
||||
|
||||
@@ -98,6 +98,13 @@ type Request struct {
|
||||
Purpose Purpose
|
||||
}
|
||||
|
||||
// SshCertificate is a credential containing a client certificate, username,
|
||||
// and SSH private key.
|
||||
type SshCertificate interface {
|
||||
SshPrivateKey
|
||||
Certificate() []byte
|
||||
}
|
||||
|
||||
// Issuer issues dynamic credentials.
|
||||
type Issuer interface {
|
||||
// Issue issues dynamic credentials for a session from the requested
|
||||
|
||||
@@ -2,8 +2,18 @@ package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -17,6 +27,8 @@ import (
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
"github.com/mikesmitty/edkey"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -25,13 +37,13 @@ var _ credential.Dynamic = (*baseCred)(nil)
|
||||
type baseCred struct {
|
||||
*Credential
|
||||
|
||||
lib *genericIssuingCredentialLibrary
|
||||
lib issuingCredentialLibrary
|
||||
secretData map[string]any
|
||||
}
|
||||
|
||||
func (bc *baseCred) Secret() credential.SecretData { return bc.secretData }
|
||||
func (bc *baseCred) Library() credential.Library { return bc.lib }
|
||||
func (bc *baseCred) Purpose() credential.Purpose { return bc.lib.Purpose }
|
||||
func (bc *baseCred) Purpose() credential.Purpose { return bc.lib.GetPurpose() }
|
||||
func (bc *baseCred) getExpiration() time.Duration { return bc.expiration }
|
||||
func (bc *baseCred) getCredential() *Credential { return bc.Credential }
|
||||
|
||||
@@ -68,7 +80,12 @@ func baseToUsrPass(ctx context.Context, bc *baseCred) (*usrPassCred, error) {
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("invalid credential type"))
|
||||
}
|
||||
|
||||
uAttr, pAttr := bc.lib.UsernameAttribute, bc.lib.PasswordAttribute
|
||||
lib, ok := bc.lib.(*genericIssuingCredentialLibrary)
|
||||
if !ok {
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("baseCred.lib is not of type genericIssuingCredentialLibrary"))
|
||||
}
|
||||
|
||||
uAttr, pAttr := lib.UsernameAttribute, lib.PasswordAttribute
|
||||
if uAttr == "" {
|
||||
uAttr = "username"
|
||||
}
|
||||
@@ -110,7 +127,12 @@ func baseToSshPriKey(ctx context.Context, bc *baseCred) (*sshPrivateKeyCred, err
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("invalid credential type"))
|
||||
}
|
||||
|
||||
uAttr, pkAttr, pAttr := bc.lib.UsernameAttribute, bc.lib.PrivateKeyAttribute, bc.lib.PrivateKeyPassphraseAttribute
|
||||
lib, ok := bc.lib.(*genericIssuingCredentialLibrary)
|
||||
if !ok {
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("baseCred.lib is not of type genericIssuingCredentialLibrary"))
|
||||
}
|
||||
|
||||
uAttr, pkAttr, pAttr := lib.UsernameAttribute, lib.PrivateKeyAttribute, lib.PrivateKeyPassphraseAttribute
|
||||
if uAttr == "" {
|
||||
uAttr = "username"
|
||||
}
|
||||
@@ -133,10 +155,18 @@ func baseToSshPriKey(ctx context.Context, bc *baseCred) (*sshPrivateKeyCred, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
type sshCertCred struct {
|
||||
*sshPrivateKeyCred
|
||||
certificate []byte
|
||||
}
|
||||
|
||||
func (c *sshCertCred) Certificate() []byte { return c.certificate }
|
||||
|
||||
var _ credential.Library = (*genericIssuingCredentialLibrary)(nil)
|
||||
|
||||
type issuingCredentialLibrary interface {
|
||||
GetPublicId() string
|
||||
credential.Library
|
||||
GetPurpose() credential.Purpose
|
||||
retrieveCredential(context.Context, errors.Op, ...credential.Option) (dynamicCred, error)
|
||||
}
|
||||
|
||||
@@ -221,6 +251,7 @@ func (pl *genericIssuingCredentialLibrary) GetDescription() string
|
||||
func (pl *genericIssuingCredentialLibrary) GetVersion() uint32 { return pl.Version }
|
||||
func (pl *genericIssuingCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
|
||||
func (pl *genericIssuingCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
|
||||
func (pl *genericIssuingCredentialLibrary) GetPurpose() credential.Purpose { return pl.Purpose }
|
||||
|
||||
func (pl *genericIssuingCredentialLibrary) CredentialType() credential.Type {
|
||||
switch ct := pl.CredType; ct {
|
||||
@@ -444,6 +475,14 @@ type privateCredentialLibraryAllTypes struct {
|
||||
PrivateKeyAttribute string
|
||||
PrivateKeyPassphraseAttribute string
|
||||
Purpose credential.Purpose `gorm:"-"`
|
||||
KeyType string
|
||||
KeyBits int
|
||||
Username string
|
||||
Ttl string
|
||||
KeyId string
|
||||
CriticalOptions []byte
|
||||
Extensions []byte
|
||||
CredLibType string
|
||||
}
|
||||
|
||||
func (pl *privateCredentialLibraryAllTypes) GetPublicId() string { return pl.PublicId }
|
||||
@@ -516,42 +555,88 @@ func (pl *privateCredentialLibraryAllTypes) clone() *privateCredentialLibraryAll
|
||||
CtClientKey: append(pl.CtClientKey[:0:0], pl.CtClientKey...),
|
||||
ClientKeyId: pl.ClientKeyId,
|
||||
Purpose: pl.Purpose,
|
||||
KeyType: pl.KeyType,
|
||||
KeyBits: pl.KeyBits,
|
||||
Username: pl.Username,
|
||||
Ttl: pl.Ttl,
|
||||
KeyId: pl.KeyId,
|
||||
CriticalOptions: pl.CriticalOptions,
|
||||
Extensions: pl.Extensions,
|
||||
CredLibType: pl.CredLibType,
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *privateCredentialLibraryAllTypes) toTypedIssuingCredentialLibrary() issuingCredentialLibrary {
|
||||
return &genericIssuingCredentialLibrary{
|
||||
PublicId: pl.PublicId,
|
||||
StoreId: pl.StoreId,
|
||||
CredType: pl.CredType,
|
||||
UsernameAttribute: pl.UsernameAttribute,
|
||||
PasswordAttribute: pl.PasswordAttribute,
|
||||
PrivateKeyAttribute: pl.PrivateKeyAttribute,
|
||||
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
|
||||
Name: pl.Name,
|
||||
Description: pl.Description,
|
||||
CreateTime: pl.CreateTime,
|
||||
UpdateTime: pl.UpdateTime,
|
||||
Version: pl.Version,
|
||||
ProjectId: pl.ProjectId,
|
||||
VaultPath: pl.VaultPath,
|
||||
HttpMethod: pl.HttpMethod,
|
||||
HttpRequestBody: pl.HttpRequestBody,
|
||||
VaultAddress: pl.VaultAddress,
|
||||
Namespace: pl.Namespace,
|
||||
CaCert: pl.CaCert,
|
||||
TlsServerName: pl.TlsServerName,
|
||||
TlsSkipVerify: pl.TlsSkipVerify,
|
||||
WorkerFilter: pl.WorkerFilter,
|
||||
TokenHmac: pl.TokenHmac,
|
||||
Token: pl.Token,
|
||||
CtToken: pl.CtToken,
|
||||
TokenKeyId: pl.TokenKeyId,
|
||||
ClientCert: pl.ClientCert,
|
||||
ClientKey: pl.ClientKey,
|
||||
CtClientKey: pl.CtClientKey,
|
||||
ClientKeyId: pl.ClientKeyId,
|
||||
Purpose: pl.Purpose,
|
||||
switch pl.CredLibType {
|
||||
case "ssh-signed-cert":
|
||||
return &sshCertIssuingCredentialLibrary{
|
||||
PublicId: pl.PublicId,
|
||||
StoreId: pl.StoreId,
|
||||
CredType: pl.CredType,
|
||||
Username: pl.Username,
|
||||
Name: pl.Name,
|
||||
Description: pl.Description,
|
||||
CreateTime: pl.CreateTime,
|
||||
UpdateTime: pl.UpdateTime,
|
||||
Version: pl.Version,
|
||||
ProjectId: pl.ProjectId,
|
||||
VaultPath: pl.VaultPath,
|
||||
VaultAddress: pl.VaultAddress,
|
||||
Namespace: pl.Namespace,
|
||||
CaCert: pl.CaCert,
|
||||
TlsServerName: pl.TlsServerName,
|
||||
TlsSkipVerify: pl.TlsSkipVerify,
|
||||
WorkerFilter: pl.WorkerFilter,
|
||||
TokenHmac: pl.TokenHmac,
|
||||
Token: pl.Token,
|
||||
CtToken: pl.CtToken,
|
||||
TokenKeyId: pl.TokenKeyId,
|
||||
ClientCert: pl.ClientCert,
|
||||
ClientKey: pl.ClientKey,
|
||||
CtClientKey: pl.CtClientKey,
|
||||
ClientKeyId: pl.ClientKeyId,
|
||||
Purpose: pl.Purpose,
|
||||
KeyType: pl.KeyType,
|
||||
KeyBits: pl.KeyBits,
|
||||
Ttl: pl.Ttl,
|
||||
KeyId: pl.KeyId,
|
||||
CriticalOptions: pl.CriticalOptions,
|
||||
Extensions: pl.Extensions,
|
||||
}
|
||||
default:
|
||||
return &genericIssuingCredentialLibrary{
|
||||
PublicId: pl.PublicId,
|
||||
StoreId: pl.StoreId,
|
||||
CredType: pl.CredType,
|
||||
UsernameAttribute: pl.UsernameAttribute,
|
||||
PasswordAttribute: pl.PasswordAttribute,
|
||||
PrivateKeyAttribute: pl.PrivateKeyAttribute,
|
||||
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
|
||||
Name: pl.Name,
|
||||
Description: pl.Description,
|
||||
CreateTime: pl.CreateTime,
|
||||
UpdateTime: pl.UpdateTime,
|
||||
Version: pl.Version,
|
||||
ProjectId: pl.ProjectId,
|
||||
VaultPath: pl.VaultPath,
|
||||
HttpMethod: pl.HttpMethod,
|
||||
HttpRequestBody: pl.HttpRequestBody,
|
||||
VaultAddress: pl.VaultAddress,
|
||||
Namespace: pl.Namespace,
|
||||
CaCert: pl.CaCert,
|
||||
TlsServerName: pl.TlsServerName,
|
||||
TlsSkipVerify: pl.TlsSkipVerify,
|
||||
WorkerFilter: pl.WorkerFilter,
|
||||
TokenHmac: pl.TokenHmac,
|
||||
Token: pl.Token,
|
||||
CtToken: pl.CtToken,
|
||||
TokenKeyId: pl.TokenKeyId,
|
||||
ClientCert: pl.ClientCert,
|
||||
ClientKey: pl.ClientKey,
|
||||
CtClientKey: pl.CtClientKey,
|
||||
ClientKeyId: pl.ClientKeyId,
|
||||
Purpose: pl.Purpose,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,3 +682,320 @@ func (m *requestMap) libIds() []string {
|
||||
func (m *requestMap) get(libraryId string) []credential.Purpose {
|
||||
return m.ids[libraryId]
|
||||
}
|
||||
|
||||
type sshCertIssuingCredentialLibrary struct {
|
||||
PublicId string
|
||||
StoreId string
|
||||
Name string
|
||||
Description string
|
||||
CreateTime *timestamp.Timestamp
|
||||
UpdateTime *timestamp.Timestamp
|
||||
Version uint32
|
||||
VaultPath string
|
||||
CredType string
|
||||
ProjectId string
|
||||
VaultAddress string
|
||||
Namespace string
|
||||
CaCert []byte
|
||||
TlsServerName string
|
||||
TlsSkipVerify bool
|
||||
WorkerFilter string
|
||||
Token TokenSecret
|
||||
CtToken []byte
|
||||
TokenHmac []byte
|
||||
TokenKeyId string
|
||||
ClientCert []byte
|
||||
ClientKey KeySecret
|
||||
CtClientKey []byte
|
||||
ClientKeyId string
|
||||
Username string
|
||||
KeyType string
|
||||
KeyBits int
|
||||
KeyId string
|
||||
Ttl string
|
||||
CriticalOptions []byte
|
||||
Extensions []byte
|
||||
Purpose credential.Purpose
|
||||
}
|
||||
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetPublicId() string { return lib.PublicId }
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetStoreId() string { return lib.StoreId }
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetName() string { return lib.Name }
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetDescription() string { return lib.Description }
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetVersion() uint32 { return lib.Version }
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetPurpose() credential.Purpose { return lib.Purpose }
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetCreateTime() *timestamp.Timestamp {
|
||||
return lib.CreateTime
|
||||
}
|
||||
|
||||
func (lib *sshCertIssuingCredentialLibrary) GetUpdateTime() *timestamp.Timestamp {
|
||||
return lib.UpdateTime
|
||||
}
|
||||
|
||||
func (lib *sshCertIssuingCredentialLibrary) CredentialType() credential.Type {
|
||||
switch ct := lib.CredType; ct {
|
||||
case "":
|
||||
return credential.UnspecifiedType
|
||||
default:
|
||||
return credential.Type(ct)
|
||||
}
|
||||
}
|
||||
|
||||
func (lib *sshCertIssuingCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
|
||||
const op = "vault.(genericIssuingCredentialLibrary).client"
|
||||
clientConfig := &clientConfig{
|
||||
Addr: lib.VaultAddress,
|
||||
Token: lib.Token,
|
||||
CaCert: lib.CaCert,
|
||||
TlsServerName: lib.TlsServerName,
|
||||
TlsSkipVerify: lib.TlsSkipVerify,
|
||||
Namespace: lib.Namespace,
|
||||
}
|
||||
|
||||
if lib.ClientKey != nil {
|
||||
clientConfig.ClientCert = lib.ClientCert
|
||||
clientConfig.ClientKey = lib.ClientKey
|
||||
}
|
||||
|
||||
client, err := vaultClientFactoryFn(ctx, clientConfig, WithWorkerFilter(lib.WorkerFilter))
|
||||
if err != nil {
|
||||
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to create vault client"))
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func generatePublicPrivateKeys(ctx context.Context, keyType string, keyBits int) (string, []byte, error) {
|
||||
const op = "vault.generatePublicPrivateKeys"
|
||||
pemBlock := pem.Block{}
|
||||
var sshKey ssh.PublicKey
|
||||
|
||||
switch keyType {
|
||||
case KeyTypeRsa:
|
||||
pemBlock.Type = "RSA PRIVATE KEY" // these values are copied from the crypto ssh library in ssh/keys.go
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, keyBits)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if sshKey, err = ssh.NewPublicKey(&key.PublicKey); err != nil {
|
||||
return "", nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
pemBlock.Bytes = x509.MarshalPKCS1PrivateKey(key)
|
||||
|
||||
case KeyTypeEd25519:
|
||||
pemBlock.Type = "OPENSSH PRIVATE KEY" // these values are copied from the crypto ssh library in ssh/keys.go
|
||||
|
||||
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if sshKey, err = ssh.NewPublicKey(pubKey); err != nil {
|
||||
return "", nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
if pemBlock.Bytes = edkey.MarshalED25519PrivateKey(privKey); pemBlock.Bytes == nil {
|
||||
return "", nil, errors.New(ctx, errors.Encode, op, "failed to marshal ed25519 private key")
|
||||
}
|
||||
|
||||
case KeyTypeEcdsa:
|
||||
pemBlock.Type = "EC PRIVATE KEY" // these values are copied from the crypto ssh library in ssh/keys.go
|
||||
|
||||
var curve elliptic.Curve
|
||||
switch keyBits {
|
||||
case 256:
|
||||
curve = elliptic.P256()
|
||||
case 384:
|
||||
curve = elliptic.P384()
|
||||
case 521:
|
||||
curve = elliptic.P521()
|
||||
default:
|
||||
return "", nil, errors.New(ctx, errors.InvalidParameter, op, "invalid KeyBits. when KeyType=ecdsa, KeyBits must be one of: 256, 384, or 521")
|
||||
}
|
||||
|
||||
key, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
if sshKey, err = ssh.NewPublicKey(&key.PublicKey); err != nil {
|
||||
return "", nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
if pemBlock.Bytes, err = x509.MarshalECPrivateKey(key); err != nil {
|
||||
return "", nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
default:
|
||||
return "", nil, errors.New(ctx, errors.InvalidParameter, op, "invalid KeyType, must be one of: \"rsa\", \"ed25519\", or \"ecdsa\"")
|
||||
}
|
||||
|
||||
publicKey := base64.StdEncoding.EncodeToString(sshKey.Marshal())
|
||||
privateKey := pem.EncodeToMemory(&pemBlock)
|
||||
if privateKey == nil {
|
||||
return "", nil, errors.New(ctx, errors.Encode, op, "failed to encode private key to PEM format")
|
||||
}
|
||||
return publicKey, privateKey, nil
|
||||
}
|
||||
|
||||
type sshCertVaultBody struct {
|
||||
KeyType string `json:"key_type,omitempty"` // must be "rsa", "ed25519", or "ecdsa"
|
||||
KeyBits int `json:"key_bits,omitempty"` // with key_type=rsa, allowed values are: 2048 (default), 3072, or 4096; with key_type=ecdsa, allowed values are: 256 (default), 384, or 521; ignored with key_type=ed25519
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
TTL string `json:"ttl,omitempty"`
|
||||
ValidPrincipals string `json:"valid_principals,omitempty"` // this needs to be "generated" off of the username provided in config
|
||||
CertType string `json:"cert_type,omitempty"` // this should always be "user"
|
||||
KeyId string `json:"key_id,omitempty"` // this will be loaded directly from lib
|
||||
CriticalOptions map[string]string `json:"critical_options,omitempty"` // this will be loaded directly from lib
|
||||
Extensions map[string]string `json:"extensions,omitempty"` // this will be loaded directly from lib
|
||||
}
|
||||
|
||||
var vaultPathRegexp = regexp.MustCompile(`^.+\/(sign|issue)\/[^\/\\\s]+$`)
|
||||
|
||||
// retrieveCredential retrieves a dynamic connection credential from Vault
|
||||
// for a specific session and connection.
|
||||
//
|
||||
// Supported options: credential.WithTemplateData
|
||||
func (lib *sshCertIssuingCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, opt ...credential.Option) (dynamicCred, error) {
|
||||
opts, err := credential.GetOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// Get the credential ID early. No need to get a secret from Vault
|
||||
// if there is no way to save it in the database.
|
||||
credId, err := newCredentialId()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
client, err := lib.client(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// build as much of the payload as we can, since sign/issue share many attributes
|
||||
// Template the username
|
||||
tplate, err := template.New(ctx, lib.Username)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
username, err := tplate.Generate(ctx, opts.WithTemplateData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
var criticalOptions map[string]string
|
||||
if lib.CriticalOptions != nil {
|
||||
if json.Unmarshal(lib.CriticalOptions, &criticalOptions) != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
|
||||
var extensions map[string]string
|
||||
if lib.Extensions != nil {
|
||||
if json.Unmarshal(lib.Extensions, &extensions) != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
|
||||
payload := sshCertVaultBody{
|
||||
ValidPrincipals: username,
|
||||
CertType: "user",
|
||||
CriticalOptions: criticalOptions,
|
||||
Extensions: extensions,
|
||||
TTL: lib.Ttl,
|
||||
KeyId: lib.KeyId,
|
||||
}
|
||||
|
||||
var privateKey credential.PrivateKey
|
||||
var secret *vault.Secret
|
||||
|
||||
match := vaultPathRegexp.FindStringSubmatch(lib.VaultPath)
|
||||
if len(match) < 2 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "vault path was not in an expected format. expected path containing \"sign\" or \"issue\"")
|
||||
}
|
||||
|
||||
// by definition, if match exists, then match[1] == "sign" or "issue"
|
||||
switch match[1] {
|
||||
case "sign":
|
||||
payload.PublicKey, privateKey, err = generatePublicPrivateKeys(ctx, lib.KeyType, lib.KeyBits)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
secret, err = client.post(ctx, lib.VaultPath, body)
|
||||
if err != nil {
|
||||
// TODO(mgaffney) 05/2021: detect if the error is because of an
|
||||
// expired or invalid token
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
|
||||
}
|
||||
|
||||
case "issue":
|
||||
payload.KeyBits = lib.KeyBits
|
||||
if lib.KeyType == KeyTypeEcdsa {
|
||||
// this is a special case where internal to boundary, we refer to the crypto
|
||||
// library name, but vault refers to it simply as "ec" for "Elliptic Curve"
|
||||
payload.KeyType = "ec"
|
||||
} else {
|
||||
payload.KeyType = lib.KeyType
|
||||
}
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
secret, err = client.post(ctx, lib.VaultPath, body)
|
||||
if err != nil {
|
||||
// TODO(mgaffney) 05/2021: detect if the error is because of an
|
||||
// expired or invalid token
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
|
||||
}
|
||||
|
||||
pk, ok := secret.Data["private_key"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a private key or response was not in the expected format")
|
||||
}
|
||||
|
||||
privateKey = []byte(pk)
|
||||
}
|
||||
|
||||
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
|
||||
cred, err := newCredential(lib.GetPublicId(), secret.LeaseID, lib.TokenHmac, leaseDuration)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
cred.PublicId = credId
|
||||
cred.IsRenewable = secret.Renewable // possibly set to just false
|
||||
|
||||
// same location for both
|
||||
cert, ok := secret.Data["signed_key"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a signed key or response was not in the expected format")
|
||||
}
|
||||
|
||||
return &sshCertCred{
|
||||
sshPrivateKeyCred: &sshPrivateKeyCred{
|
||||
baseCred: &baseCred{
|
||||
Credential: cred,
|
||||
lib: lib,
|
||||
secretData: secret.Data,
|
||||
},
|
||||
username: username,
|
||||
privateKey: privateKey,
|
||||
},
|
||||
certificate: []byte(cert),
|
||||
}, nil
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
@@ -10,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/scheduler"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
"github.com/hashicorp/boundary/internal/util/template"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -63,7 +67,7 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
|
||||
opts = append(opts, WithClientCert(clientCert))
|
||||
}
|
||||
|
||||
_, token := v.CreateToken(t)
|
||||
_, token := v.CreateToken(t, WithTokenPeriod(time.Hour))
|
||||
|
||||
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...)
|
||||
assert.NoError(err)
|
||||
@@ -1215,3 +1219,171 @@ func TestBaseToSshPriKey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_sshCertIssuingCredentialLibrary_retrieveCredential(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// create test vault server
|
||||
v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS), WithVaultVersion("1.12.2"))
|
||||
require.NotNil(t, v)
|
||||
|
||||
vc := v.client(t).cl
|
||||
mounts, err := vc.Sys().ListMounts()
|
||||
assert.NoError(t, err)
|
||||
require.NotEmpty(t, mounts)
|
||||
beforeCount := len(mounts)
|
||||
|
||||
// enable ssh secrets engine
|
||||
v.MountSSH(t, WithAllowedExtension("permit-pty"))
|
||||
mounts, err = vc.Sys().ListMounts()
|
||||
fmt.Printf("mounts: %#v\n", mounts)
|
||||
assert.NoError(t, err)
|
||||
require.NotEmpty(t, mounts)
|
||||
afterCount := len(mounts)
|
||||
assert.Greater(t, afterCount, beforeCount)
|
||||
|
||||
// create and setup db
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
sec, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "ssh"}), WithTokenPeriod(time.Hour))
|
||||
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, repo)
|
||||
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), v.Addr, token, sec.Auth.Accessor)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
vaulthPath string
|
||||
opts []Option
|
||||
retOpts []credential.Option
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
name: "vault sign boundary ed25519 key",
|
||||
username: "username",
|
||||
vaulthPath: "ssh/sign/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeEd25519)},
|
||||
},
|
||||
{
|
||||
name: "vault sign boundary rsa(4096) key",
|
||||
username: "username-2-electric-boogaloo",
|
||||
vaulthPath: "ssh/sign/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(4096)},
|
||||
},
|
||||
{
|
||||
name: "vault sign boundary ec(521) key",
|
||||
username: "username-3-the-namening",
|
||||
vaulthPath: "ssh/sign/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(521)},
|
||||
},
|
||||
{
|
||||
name: "vault issue ed25519 cert",
|
||||
username: "username-4-revengeance",
|
||||
vaulthPath: "ssh/issue/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeEd25519)},
|
||||
},
|
||||
{
|
||||
name: "vault issue rsa(4096) cert",
|
||||
username: "username-5-this-time-its-personal",
|
||||
vaulthPath: "ssh/issue/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(4096)},
|
||||
},
|
||||
{
|
||||
name: "vault issue ec(521) cert",
|
||||
username: "username-6-the-holiday-episode",
|
||||
vaulthPath: "ssh/issue/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(521)},
|
||||
},
|
||||
{
|
||||
name: "vault issue rsa(2048) cert with critical options and extensions",
|
||||
username: "username-7-because-789",
|
||||
vaulthPath: "ssh/issue/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(2048), WithCriticalOptions("{ \"force-commnad\": \"/bin/some-script\" }"), WithExtensions("{ \"permit-pty\": \"\" }")},
|
||||
},
|
||||
{
|
||||
name: "vault sign boundary rsa(3072) key with critical options and extensions",
|
||||
username: "username-7-because-789",
|
||||
vaulthPath: "ssh/sign/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(3072), WithCriticalOptions("{ \"force-commnad\": \"/bin/some-script\" }"), WithExtensions("{ \"permit-pty\": \"\" }")},
|
||||
},
|
||||
{
|
||||
name: "vault issue ec(256) cert with template username",
|
||||
username: "username-8-{{ .User.Name }}",
|
||||
expected: map[string]string{
|
||||
"username": "username-8-revenge-of-the-template",
|
||||
},
|
||||
vaulthPath: "ssh/issue/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(256)},
|
||||
retOpts: []credential.Option{credential.WithTemplateData(template.Data{User: template.User{Name: util.Pointer("revenge-of-the-template")}})},
|
||||
},
|
||||
{
|
||||
name: "vault issue ec(384) cert with disallowed extension",
|
||||
username: "username-10-because-789",
|
||||
expected: map[string]string{
|
||||
"error": "extensions [permit-port-forwarding] are not on allowed list",
|
||||
},
|
||||
vaulthPath: "ssh/issue/boundary",
|
||||
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(384), WithExtensions("{ \"permit-port-forwarding\": \"\" }")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
// create ssh library
|
||||
lib, err := NewSSHCertificateCredentialLibrary(cs.GetPublicId(), tt.vaulthPath, tt.username, tt.opts...)
|
||||
require.NoError(err)
|
||||
require.NotNil(lib)
|
||||
lib.PublicId, err = newSSHCertificateCredentialLibraryId()
|
||||
require.NoError(err)
|
||||
|
||||
_, err = rw.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
return iw.Create(ctx, lib)
|
||||
},
|
||||
)
|
||||
require.NoError(err)
|
||||
|
||||
req := credential.Request{
|
||||
SourceId: lib.GetPublicId(),
|
||||
Purpose: "doesn't matter",
|
||||
}
|
||||
|
||||
libs, err := repo.getIssueCredLibraries(ctx, []credential.Request{req})
|
||||
require.NoError(err)
|
||||
require.NotEmpty(libs)
|
||||
require.Equal(1, len(libs))
|
||||
|
||||
cred, err := libs[0].retrieveCredential(ctx, "op", tt.retOpts...)
|
||||
if retErr, ok := tt.expected["error"]; ok {
|
||||
require.ErrorContains(err, retErr)
|
||||
return // retrieveCredential failed (as expected) don't do the rest of the checks
|
||||
}
|
||||
require.NoError(err)
|
||||
|
||||
cert, ok := cred.(*sshCertCred)
|
||||
require.True(ok)
|
||||
|
||||
if usr, ok := tt.expected["username"]; ok {
|
||||
require.Equal(usr, cert.username)
|
||||
}
|
||||
|
||||
// TODO: somehow check that the ssh cert matches the private key
|
||||
// for now, manually check that private key and cert match and that sign/issue both return correct formats
|
||||
fmt.Printf("test: %s\npriv:\n%s\ncert:\n%s\n", tt.name, string(cert.privateKey), string(cert.certificate))
|
||||
|
||||
// ensure credential matches expected SshCertificate
|
||||
_, ok = cred.(credential.SshCertificate)
|
||||
require.True(ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,14 @@ func gotNewServer(t testing.TB, opt ...TestOption) *TestVaultServer {
|
||||
pool: pool,
|
||||
}
|
||||
|
||||
vaultVersion := DefaultVaultVersion
|
||||
if opts.vaultVersion != "" {
|
||||
vaultVersion = opts.vaultVersion
|
||||
}
|
||||
|
||||
dockerOptions := &dockertest.RunOptions{
|
||||
Repository: "vault",
|
||||
Tag: DefaultVaultVersion,
|
||||
Tag: vaultVersion,
|
||||
Env: []string{fmt.Sprintf("VAULT_DEV_ROOT_TOKEN_ID=%s", server.RootToken)},
|
||||
}
|
||||
|
||||
|
||||
@@ -140,10 +140,10 @@ func TestCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping.Wrapper, stor
|
||||
return libs
|
||||
}
|
||||
|
||||
// TestSSHCertificateCredentialLibraries creates count number of vault credential
|
||||
// libraries in the provided DB with the provided store id. If any errors
|
||||
// are encountered during the creation of the credential libraries, the
|
||||
// test will fail.
|
||||
// TestSSHCertificateCredentialLibraries creates count number of vault ssh
|
||||
// certificate credential libraries in the provided DB with the provided store
|
||||
// id. If any errors are encountered during the creation of the credential
|
||||
// libraries, the test will fail.
|
||||
func TestSSHCertificateCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping.Wrapper, storeId string, count int) []*SSHCertificateCredentialLibrary {
|
||||
t.Helper()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
@@ -151,7 +151,7 @@ func TestSSHCertificateCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping
|
||||
var libs []*SSHCertificateCredentialLibrary
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
lib, err := NewSSHCertificateCredentialLibrary(storeId, fmt.Sprintf("vault/path%d", i), "username", withKeyType(KeyTypeEd25519))
|
||||
lib, err := NewSSHCertificateCredentialLibrary(storeId, fmt.Sprintf("ssh/sign/role-%d", i), "username", WithKeyType(KeyTypeEd25519))
|
||||
assert.NoError(err)
|
||||
require.NotNil(lib)
|
||||
id, err := newSSHCertificateCredentialLibraryId()
|
||||
@@ -509,17 +509,19 @@ type TestOption func(testing.TB, *testOptions)
|
||||
|
||||
// options = how options are represented
|
||||
type testOptions struct {
|
||||
orphan bool
|
||||
periodic bool
|
||||
renewable bool
|
||||
policies []string
|
||||
mountPath string
|
||||
roleName string
|
||||
vaultTLS TestVaultTLS
|
||||
dockerNetwork bool
|
||||
skipCleanup bool
|
||||
tokenPeriod time.Duration
|
||||
clientKey *ecdsa.PrivateKey
|
||||
orphan bool
|
||||
periodic bool
|
||||
renewable bool
|
||||
policies []string
|
||||
allowedExtensions []string
|
||||
mountPath string
|
||||
roleName string
|
||||
vaultTLS TestVaultTLS
|
||||
dockerNetwork bool
|
||||
skipCleanup bool
|
||||
tokenPeriod time.Duration
|
||||
clientKey *ecdsa.PrivateKey
|
||||
vaultVersion string
|
||||
}
|
||||
|
||||
func getDefaultTestOptions(t testing.TB) testOptions {
|
||||
@@ -625,6 +627,24 @@ func WithDontCleanUp() TestOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithVaultVersion sets the version of vault that will be started.
|
||||
// defaults to the value stored in vault.supported.DefaultVaultVersion
|
||||
func WithVaultVersion(s string) TestOption {
|
||||
return func(t testing.TB, o *testOptions) {
|
||||
t.Helper()
|
||||
o.vaultVersion = s
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllowedExtension tells vault to allow a specific SSH extension
|
||||
// to be used by vault's ssh secrets engine
|
||||
func WithAllowedExtension(e string) TestOption {
|
||||
return func(t testing.TB, o *testOptions) {
|
||||
t.Helper()
|
||||
o.allowedExtensions = append(o.allowedExtensions, e)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrphanToken sets the token orphan option to b.
|
||||
// The orphan option is true by default.
|
||||
func TestOrphanToken(b bool) TestOption {
|
||||
@@ -784,6 +804,95 @@ func (v *TestVaultServer) addPolicy(t testing.TB, name string, pc pathCapabiliti
|
||||
require.NoError(vc.Sys().PutPolicy(name, policy))
|
||||
}
|
||||
|
||||
// MountSSH mounts the Vault SSH secret engine and initializes it by
|
||||
// generating a root certificate authority and creating a default role on
|
||||
// the mount. The root CA is returned.
|
||||
//
|
||||
// The default mount path is ssh and the default role name is boundary.
|
||||
// WithTestMountPath, WithTestRoleName, and WithAllowedExtension are the
|
||||
// test options supported. extensions are combined into allowed_extensions.
|
||||
// allowed_extensions defaults to "*" (allow all)
|
||||
// The role is defined as:
|
||||
//
|
||||
// {
|
||||
// "key_type": "ca",
|
||||
// "allowed_users": "*",
|
||||
// "allowed_extensions": "*",
|
||||
// "allow_user_certificates": true,
|
||||
// "ttl": "12h0m0s"
|
||||
// }
|
||||
//
|
||||
// MountSSH also adds a Vault policy named 'ssh' to v and adds it to the
|
||||
// standard set of polices attached to tokens created with v.CreateToken.
|
||||
// The policy is defined as:
|
||||
//
|
||||
// path "mountPath/*" {
|
||||
// capabilities = ["create", "read", "update", "delete", "list"]
|
||||
// }
|
||||
func (v *TestVaultServer) MountSSH(t testing.TB, opt ...TestOption) *vault.Secret {
|
||||
require := require.New(t)
|
||||
opts := getTestOpts(t, opt...)
|
||||
vc := v.client(t).cl
|
||||
|
||||
// Mount SSH
|
||||
maxTTL := 24 * time.Hour
|
||||
if t, ok := t.(*testing.T); ok {
|
||||
if deadline, ok := t.Deadline(); ok {
|
||||
maxTTL = time.Until(deadline) * 2
|
||||
}
|
||||
}
|
||||
|
||||
defaultTTL := maxTTL / 2
|
||||
t.Logf("maxTTL: %s, defaultTTL: %s", maxTTL, defaultTTL)
|
||||
mountInput := &vault.MountInput{
|
||||
Type: "ssh",
|
||||
Description: t.Name(),
|
||||
Config: vault.MountConfigInput{
|
||||
DefaultLeaseTTL: defaultTTL.String(),
|
||||
MaxLeaseTTL: maxTTL.String(),
|
||||
},
|
||||
}
|
||||
mountPath := opts.mountPath
|
||||
if mountPath == "" {
|
||||
mountPath = "ssh/"
|
||||
}
|
||||
// this is the call that actually enables the secrets engine
|
||||
require.NoError(vc.Sys().Mount(mountPath, mountInput))
|
||||
|
||||
policyPath := fmt.Sprintf("%s*", mountPath)
|
||||
pc := pathCapabilities{
|
||||
policyPath: createCapability | readCapability | updateCapability | deleteCapability | listCapability,
|
||||
}
|
||||
v.addPolicy(t, "ssh", pc)
|
||||
|
||||
// Generate a root CA
|
||||
caPath := path.Join(mountPath, "config/ca")
|
||||
caOptions := map[string]any{
|
||||
"generate_signing_key": true,
|
||||
}
|
||||
s, err := vc.Logical().Write(caPath, caOptions)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(s)
|
||||
|
||||
// Create default role
|
||||
rolePath := path.Join(mountPath, "roles", opts.roleName)
|
||||
allowExtensions := "*"
|
||||
if len(opts.allowedExtensions) > 0 {
|
||||
allowExtensions = strings.Join(opts.allowedExtensions, ",")
|
||||
}
|
||||
roleOptions := map[string]any{
|
||||
"key_type": "ca",
|
||||
"allowed_users": "*",
|
||||
"allowed_extensions": allowExtensions,
|
||||
"allow_user_certificates": true,
|
||||
"ttl": defaultTTL.String(),
|
||||
}
|
||||
_, err = vc.Logical().Write(rolePath, roleOptions)
|
||||
require.NoError(err)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// MountPKI mounts the Vault PKI secret engine and initializes it by
|
||||
// generating a root certificate authority and creating a default role on
|
||||
// the mount. The root CA is returned.
|
||||
|
||||
+1
@@ -188,6 +188,7 @@ begin;
|
||||
'The view returns the current token for the store, if the Vault token has expired this view will return an empty token_hmac and a token_status of ''expired'' '
|
||||
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
-- Replaced in 63/02_add_ssh_cert_to_vault_cred_library_view.up.sql
|
||||
create view credential_vault_library_issue_credentials as
|
||||
with
|
||||
password_override (library_id, username_attribute, password_attribute) as (
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
begin;
|
||||
|
||||
-- Replaces view from 56/02_add_data_key_foreign_key_references.up.sql
|
||||
drop view credential_vault_library_issue_credentials;
|
||||
create view credential_vault_library_issue_credentials as
|
||||
with
|
||||
password_override (library_id, username_attribute, password_attribute) as (
|
||||
select library_id,
|
||||
nullif(username_attribute, wt_to_sentinel('no override')),
|
||||
nullif(password_attribute, wt_to_sentinel('no override'))
|
||||
from credential_vault_library_username_password_mapping_override
|
||||
),
|
||||
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
|
||||
select library_id,
|
||||
nullif(username_attribute, wt_to_sentinel('no override')),
|
||||
nullif(private_key_attribute, wt_to_sentinel('no override')),
|
||||
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
|
||||
from credential_vault_library_ssh_private_key_mapping_override
|
||||
)
|
||||
select library.public_id as public_id,
|
||||
library.store_id as store_id,
|
||||
library.name as name,
|
||||
library.description as description,
|
||||
library.create_time as create_time,
|
||||
library.update_time as update_time,
|
||||
library.version as version,
|
||||
library.vault_path as vault_path,
|
||||
library.http_method as http_method,
|
||||
library.http_request_body as http_request_body,
|
||||
library.credential_type as credential_type,
|
||||
null as key_type,
|
||||
null as key_bits,
|
||||
null as username,
|
||||
null as ttl,
|
||||
null as key_id,
|
||||
null as critical_options,
|
||||
null as extensions,
|
||||
store.project_id as project_id,
|
||||
store.vault_address as vault_address,
|
||||
store.namespace as namespace,
|
||||
store.ca_cert as ca_cert,
|
||||
store.tls_server_name as tls_server_name,
|
||||
store.tls_skip_verify as tls_skip_verify,
|
||||
store.worker_filter as worker_filter,
|
||||
store.ct_token as ct_token, -- encrypted
|
||||
store.token_hmac as token_hmac,
|
||||
store.token_status as token_status,
|
||||
store.token_key_id as token_key_id,
|
||||
store.client_cert as client_cert,
|
||||
store.ct_client_key as ct_client_key, -- encrypted
|
||||
store.client_key_id as client_key_id,
|
||||
coalesce(upasso.username_attribute,sshpk.username_attribute)
|
||||
as username_attribute,
|
||||
upasso.password_attribute as password_attribute,
|
||||
sshpk.private_key_attribute as private_key_attribute,
|
||||
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute,
|
||||
'generic' as cred_lib_type -- used to switch on
|
||||
from credential_vault_library library
|
||||
join credential_vault_store_client store
|
||||
on library.store_id = store.public_id
|
||||
left join password_override upasso
|
||||
on library.public_id = upasso.library_id
|
||||
left join ssh_private_key_override sshpk
|
||||
on library.public_id = sshpk.library_id
|
||||
union
|
||||
select library.public_id as public_id,
|
||||
library.store_id as store_id,
|
||||
library.name as name,
|
||||
library.description as description,
|
||||
library.create_time as create_time,
|
||||
library.update_time as update_time,
|
||||
library.version as version,
|
||||
library.vault_path as vault_path,
|
||||
null as http_method,
|
||||
null as http_request_body,
|
||||
library.credential_type as credential_type,
|
||||
library.key_type as key_type,
|
||||
library.key_bits as key_bits,
|
||||
library.username as username,
|
||||
library.ttl as ttl,
|
||||
library.key_id as key_id,
|
||||
library.critical_options as critical_options,
|
||||
library.extensions as extensions,
|
||||
store.project_id as project_id,
|
||||
store.vault_address as vault_address,
|
||||
store.namespace as namespace,
|
||||
store.ca_cert as ca_cert,
|
||||
store.tls_server_name as tls_server_name,
|
||||
store.tls_skip_verify as tls_skip_verify,
|
||||
store.worker_filter as worker_filter,
|
||||
store.ct_token as ct_token, -- encrypted
|
||||
store.token_hmac as token_hmac,
|
||||
store.token_status as token_status,
|
||||
store.token_key_id as token_key_id,
|
||||
store.client_cert as client_cert,
|
||||
store.ct_client_key as ct_client_key, -- encrypted
|
||||
store.client_key_id as client_key_id,
|
||||
null as username_attribute,
|
||||
null as password_attribute,
|
||||
null as private_key_attribute,
|
||||
null as private_key_passphrase_attribute,
|
||||
'ssh-signed-cert' as cred_lib_type -- used to switch on
|
||||
from credential_vault_ssh_cert_library library
|
||||
join credential_vault_store_client store
|
||||
on library.store_id = store.public_id;
|
||||
comment on view credential_vault_library_issue_credentials is
|
||||
'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
|
||||
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
|
||||
'This view should not be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
commit;
|
||||
Reference in new issue
Block a user