Skip to content

Commit fe514b7

Browse files
committed
allow for wrapped vault auth tokens
1 parent d788c9f commit fe514b7

File tree

14 files changed

+466
-231
lines changed

14 files changed

+466
-231
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ perms-table:
136136
.PHONY: gen
137137
gen: cleangen proto api cli perms-table fmt copywrite
138138

139+
.PHONY: gen-offline
140+
gen-offline: cleangen protobuild api cli perms-table fmt copywrite
141+
139142
### oplog requires protoc-gen-go v1.20.0 or later
140143
# GO111MODULE=on go get -u github.com/golang/protobuf/[email protected]
141144
.PHONY: proto

api/credentialstores/option.gen.go

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/credentialstores/vault_credential_store_attributes.gen.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cmd/commands/credentialstorescmd/vault_funcs.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
vaultTokenFlagName = "vault-token"
2828
clientCertificateFlagName = "vault-client-certificate"
2929
clientCertificateKeyFlagName = "vault-client-certificate-key"
30+
tokenWrappedFlagName = "vault-token-wrapped"
3031
workerFilterFlagName = "worker-filter"
3132
)
3233

@@ -40,6 +41,7 @@ type extraVaultCmdVars struct {
4041
flagTlsServerName string
4142
flagTlsSkipVerify bool
4243
flagWorkerFilter string
44+
flagTokenWrapped bool
4345
}
4446

4547
func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
@@ -53,6 +55,7 @@ func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
5355
vaultTokenFlagName,
5456
clientCertificateFlagName,
5557
clientCertificateKeyFlagName,
58+
tokenWrappedFlagName,
5659
workerFilterFlagName,
5760
},
5861
}
@@ -113,6 +116,12 @@ func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSe
113116
Target: &c.flagClientCertKey,
114117
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.`,
115118
})
119+
case tokenWrappedFlagName:
120+
f.BoolVar(&base.BoolVar{
121+
Name: tokenWrappedFlagName,
122+
Target: &c.flagTokenWrapped,
123+
Usage: "Indicates that the provided vault token was wrapped using vault's response wrapping.",
124+
})
116125
case workerFilterFlagName:
117126
f.StringVar(&base.StringVar{
118127
Name: workerFilterFlagName,
@@ -179,6 +188,9 @@ func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]c
179188
if c.flagTlsSkipVerify {
180189
*opts = append(*opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(c.flagTlsSkipVerify))
181190
}
191+
if c.flagTokenWrapped {
192+
*opts = append(*opts, credentialstores.WithVaultCredentialStoreTokenWrapped(c.flagTokenWrapped))
193+
}
182194

183195
return true
184196
}

internal/credential/vault/credential_store.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func NewCredentialStore(projectId string, vaultAddress string, token TokenSecret
4747
TlsServerName: opts.withTlsServerName,
4848
TlsSkipVerify: opts.withTlsSkipVerify,
4949
WorkerFilter: opts.withWorkerFilter,
50+
TokenWrapped: opts.withTokenWrapped,
5051
},
5152
}
5253
return cs, nil
@@ -207,3 +208,44 @@ func (cs *CredentialStore) softDeleteQuery() (query string, queryValues []any) {
207208
}
208209
return
209210
}
211+
212+
// Unwrap assumes that the cs.inputToken has been wrapped by vault and attempts
213+
// to unwrap it. Returns errors if the token is not wrapped, has been unwrapped
214+
// previously, is expired, or is invalid. Paths are checked for equality, and
215+
// return an error if they do not match. See
216+
// https://developer.hashicorp.com/vault/docs/concepts/response-wrapping#response-wrapping-token-validation
217+
func (cs *CredentialStore) Unwrap(ctx context.Context) error {
218+
const op = "vault.(CredentialStore).Unwrap"
219+
// we cannot do a standard client.lookupToken here, as it is a wrapping token
220+
client, err := cs.client(ctx)
221+
if err != nil {
222+
return errors.Wrap(ctx, err, op, errors.WithMsg("usnable to create vault client"))
223+
}
224+
res, err := client.lookupWrappedToken(ctx, string(cs.inputToken))
225+
if err != nil {
226+
if errors.Match(errors.T(errors.VaultCredentialRequest), err) {
227+
// TODO: we received an error from vault for the lookup, and we should probably fire an alert or log
228+
// this is considered less high-risk than the path matching error, but is still grounds for investigation
229+
}
230+
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup wrapped token"))
231+
}
232+
233+
// since this unwrap function lives within CredentialStore, we always know what the expect path is
234+
const vaultAuthTokenCreationPath = "auth/token/create"
235+
if res.CreationPath != vaultAuthTokenCreationPath {
236+
// TODO: fire an alert here that the wrapped token was potentially tampered with
237+
return errors.New(ctx, errors.VaultWrappedSecretPathInvalid, op, "vault token creation path did not match the expected path")
238+
}
239+
240+
sec, err := client.unwrap(ctx, string(cs.inputToken))
241+
if err != nil {
242+
// TODO: we received an error from vault for the unwrapping, and we should probably fire an alert or log
243+
// again, this is considered less high-risk than the path matching error, but is still grounds for investigation
244+
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to unwrap token"))
245+
}
246+
247+
// sec will be in the format returned by the vault auth token create endpoint, so we can parse it as that api response
248+
cs.inputToken = TokenSecret(sec.Auth.ClientToken)
249+
250+
return nil
251+
}

internal/credential/vault/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type options struct {
3131
withMethod Method
3232
withRequestBody []byte
3333
withCredentialType globals.CredentialType
34+
withTokenWrapped bool
3435

3536
withOverrideUsernameAttribute string
3637
withOverridePasswordAttribute string
@@ -146,6 +147,13 @@ func WithCredentialType(t globals.CredentialType) Option {
146147
}
147148
}
148149

150+
// WithTokenWrapped signals that the provided vault token must be unwrapped.
151+
func WithTokenWrapped(wrapped bool) Option {
152+
return func(o *options) {
153+
o.withTokenWrapped = wrapped
154+
}
155+
}
156+
149157
// WithOverrideUsernameAttribute provides the name of an attribute in the
150158
// Data field of a Vault api.Secret that maps to a username value.
151159
func WithOverrideUsernameAttribute(s string) Option {

internal/credential/vault/repository_credential_store.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ func (r *Repository) CreateCredentialStore(ctx context.Context, cs *CredentialSt
6868

6969
cs = cs.clone()
7070

71+
if cs.TokenWrapped {
72+
if err := cs.Unwrap(ctx); err != nil {
73+
return nil, errors.Wrap(ctx, err, op)
74+
}
75+
}
76+
7177
id, err := newCredentialStoreId(ctx)
7278
if err != nil {
7379
return nil, errors.Wrap(ctx, err, op)
@@ -498,6 +504,12 @@ func (r *Repository) UpdateCredentialStore(ctx context.Context, cs *CredentialSt
498504
}
499505
}
500506
if updateToken {
507+
if cs.TokenWrapped {
508+
if err := cs.Unwrap(ctx); err != nil {
509+
return nil, 0, errors.Wrap(ctx, err, op)
510+
}
511+
}
512+
501513
renewedToken, err := client.renewToken(ctx)
502514
if err != nil {
503515
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to renew vault token"))

0 commit comments

Comments
 (0)