Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow for wrapped vault auth tokens #4891

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ perms-table:
.PHONY: gen
gen: cleangen proto api cli perms-table fmt copywrite

.PHONY: gen-offline
gen-offline: cleangen protobuild api cli perms-table fmt copywrite

Comment on lines +139 to +141
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this definitely shouldn't be in this pr, but makes it easier for when you're working without internet

### oplog requires protoc-gen-go v1.20.0 or later
# GO111MODULE=on go get -u github.com/golang/protobuf/[email protected]
.PHONY: proto
Expand Down
24 changes: 24 additions & 0 deletions api/credentialstores/option.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions internal/cmd/commands/credentialstorescmd/vault_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
vaultTokenFlagName = "vault-token"
clientCertificateFlagName = "vault-client-certificate"
clientCertificateKeyFlagName = "vault-client-certificate-key"
tokenWrappedFlagName = "vault-token-wrapped"
workerFilterFlagName = "worker-filter"
)

Expand All @@ -40,6 +41,7 @@ type extraVaultCmdVars struct {
flagTlsServerName string
flagTlsSkipVerify bool
flagWorkerFilter string
flagTokenWrapped bool
}

func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
Expand All @@ -53,6 +55,7 @@ func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
vaultTokenFlagName,
clientCertificateFlagName,
clientCertificateKeyFlagName,
tokenWrappedFlagName,
workerFilterFlagName,
},
}
Expand Down Expand Up @@ -113,6 +116,12 @@ func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSe
Target: &c.flagClientCertKey,
Usage: `The client certificate's private key to use when boundary connects to vault for this store. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.`,
})
case tokenWrappedFlagName:
f.BoolVar(&base.BoolVar{
Name: tokenWrappedFlagName,
Target: &c.flagTokenWrapped,
Usage: "Indicates that the provided vault token was wrapped using vault's response wrapping.",
})
case workerFilterFlagName:
f.StringVar(&base.StringVar{
Name: workerFilterFlagName,
Expand Down Expand Up @@ -179,6 +188,9 @@ func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]c
if c.flagTlsSkipVerify {
*opts = append(*opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(c.flagTlsSkipVerify))
}
if c.flagTokenWrapped {
*opts = append(*opts, credentialstores.WithVaultCredentialStoreTokenWrapped(c.flagTokenWrapped))
}

return true
}
Expand Down
42 changes: 42 additions & 0 deletions internal/credential/vault/credential_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
TlsServerName: opts.withTlsServerName,
TlsSkipVerify: opts.withTlsSkipVerify,
WorkerFilter: opts.withWorkerFilter,
TokenWrapped: opts.withTokenWrapped,
},
}
return cs, nil
Expand Down Expand Up @@ -207,3 +208,44 @@
}
return
}

// Unwrap assumes that the cs.inputToken has been wrapped by vault and attempts
// to unwrap it. Returns errors if the token is not wrapped, has been unwrapped
// previously, is expired, or is invalid. Paths are checked for equality, and
// return an error if they do not match. See
// https://developer.hashicorp.com/vault/docs/concepts/response-wrapping#response-wrapping-token-validation
func (cs *CredentialStore) Unwrap(ctx context.Context) error {
const op = "vault.(CredentialStore).Unwrap"
// we cannot do a standard client.lookupToken here, as it is a wrapping token
client, err := cs.client(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("usnable to create vault client"))
}
res, err := client.lookupWrappedToken(ctx, string(cs.inputToken))
if err != nil {
if errors.Match(errors.T(errors.VaultCredentialRequest), err) {

Check failure on line 226 in internal/credential/vault/credential_store.go

View workflow job for this annotation

GitHub Actions / Run Linter

SA9003: empty branch (staticcheck)
// TODO: we received an error from vault for the lookup, and we should probably fire an alert or log
// this is considered less high-risk than the path matching error, but is still grounds for investigation
}
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup wrapped token"))
}

// since this unwrap function lives within CredentialStore, we always know what the expect path is
const vaultAuthTokenCreationPath = "auth/token/create"
if res.CreationPath != vaultAuthTokenCreationPath {
// TODO: fire an alert here that the wrapped token was potentially tampered with
return errors.New(ctx, errors.VaultWrappedSecretPathInvalid, op, "vault token creation path did not match the expected path")
}

sec, err := client.unwrap(ctx, string(cs.inputToken))
if err != nil {
// TODO: we received an error from vault for the unwrapping, and we should probably fire an alert or log
// again, this is considered less high-risk than the path matching error, but is still grounds for investigation
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to unwrap token"))
}

// sec will be in the format returned by the vault auth token create endpoint, so we can parse it as that api response
cs.inputToken = TokenSecret(sec.Auth.ClientToken)

return nil
}
8 changes: 8 additions & 0 deletions internal/credential/vault/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type options struct {
withMethod Method
withRequestBody []byte
withCredentialType globals.CredentialType
withTokenWrapped bool

withOverrideUsernameAttribute string
withOverridePasswordAttribute string
Expand Down Expand Up @@ -146,6 +147,13 @@ func WithCredentialType(t globals.CredentialType) Option {
}
}

// WithTokenWrapped signals that the provided vault token must be unwrapped.
func WithTokenWrapped(wrapped bool) Option {
return func(o *options) {
o.withTokenWrapped = wrapped
}
}

// WithOverrideUsernameAttribute provides the name of an attribute in the
// Data field of a Vault api.Secret that maps to a username value.
func WithOverrideUsernameAttribute(s string) Option {
Expand Down
12 changes: 12 additions & 0 deletions internal/credential/vault/repository_credential_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ func (r *Repository) CreateCredentialStore(ctx context.Context, cs *CredentialSt

cs = cs.clone()

if cs.TokenWrapped {
if err := cs.Unwrap(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}

id, err := newCredentialStoreId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
Expand Down Expand Up @@ -498,6 +504,12 @@ func (r *Repository) UpdateCredentialStore(ctx context.Context, cs *CredentialSt
}
}
if updateToken {
if cs.TokenWrapped {
if err := cs.Unwrap(ctx); err != nil {
return nil, 0, errors.Wrap(ctx, err, op)
}
}

renewedToken, err := client.renewToken(ctx)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to renew vault token"))
Expand Down
Loading
Loading