Commit 662af2b6 authored by Angel Garbarino's avatar Angel Garbarino
Browse files

Merge branch 'main' into ui/kv-fix-number-key

parents afc2c073 d01ae8bf
No related merge requests found
Showing with 975 additions and 241 deletions
+975 -241
This diff is collapsed.
This diff is collapsed.
restore_yarn_cache:
steps:
- restore_cache:
name: Restore yarn cache
key: &YARN_LOCK_CACHE_KEY yarn-lock-v6-{{ checksum "ui/yarn.lock" }}
save_yarn_cache:
steps:
- save_cache:
name: Save yarn cache
key: *YARN_LOCK_CACHE_KEY
paths:
- ui/node_modules
# allows restoring go mod caches by incomplete prefix. This is useful when re-generating
# cache, but not when running builds and tests that require an exact match.
restore_go_mod_cache_permissive:
steps:
- restore_cache:
name: Restore closest matching go modules cache
keys:
- &gocachekey v1.3-{{checksum "go.sum"}}-{{checksum "sdk/go.sum"}}-{{checksum "api/go.sum"}}
- v1.3-{{checksum "go.sum"}}-{{checksum "sdk/go.sum"}}
- v1.3-{{checksum "go.sum"}}
restore_go_mod_cache:
steps:
- restore_cache:
name: Restore exact go modules cache
keys:
- *gocachekey
save_go_mod_cache:
steps:
- save_cache:
name: Save go modules cache
key: *gocachekey
paths:
- /go/pkg/mod
refresh_go_mod_cache:
steps:
- restore_go_mod_cache_permissive
- run:
name: go mod download
command: |
# go list ./... forces downloading some additional versions of modules that 'go mod
# download' misses. We need this because we make use of go list itself during
# code generation in later builds that rely on this module cache.
go list ./...
go mod download -json
( cd sdk && go mod download -json; )
( cd api && go mod download -json; )
- run:
name: Verify downloading modules did not modify any files
command: |
git --no-pager diff --exit-code || {
echo "ERROR: Files modified by go mod download, see above."
exit 1
}
- save_go_mod_cache
restore_yarn_cache:
steps:
- restore_cache:
key: &YARN_LOCK_CACHE_KEY yarn-lock-v6-{{ checksum "ui/yarn.lock" }}
save_yarn_cache:
steps:
- save_cache:
key: *YARN_LOCK_CACHE_KEY
paths:
- ui/node_modules
steps:
- add_ssh_keys:
fingerprints:
- "0e:03:77:f4:e2:c3:56:c2:53:6a:03:e1:31:91:2f:06"
- run: |
git config --global url."git@github.com:".insteadOf https://github.com/
......@@ -21,8 +21,9 @@ parameters:
arch:
type: string
# Only supported for use_docker=false, and only other value allowed is 386
default: amd64
default: amd64 # must be 386 or amd64
steps:
- configure-git
- run:
name: Compute test cache key
command: |
......@@ -30,9 +31,12 @@ steps:
- restore_cache:
keys:
- go-test-cache-date-v1-{{ checksum "/tmp/go-cache-key" }}
- restore_go_mod_cache
- run:
name: Run Go tests
no_output_timeout: 60m
environment:
GOPRIVATE: 'github.com/hashicorp/*'
command: |
set -x
......@@ -102,9 +106,17 @@ steps:
TEST_DOCKER_NETWORK_ID=$(docker network create vaulttest)
fi
# Start a docker testcontainer to run the tests in
docker run -d -e TEST_DOCKER_NETWORK_ID \
-e DOCKER_CERT_PATH -e DOCKER_HOST -e DOCKER_MACHINE_NAME -e DOCKER_TLS_VERIFY -e NO_PROXY \
docker run -d \
-e TEST_DOCKER_NETWORK_ID \
-e GOPRIVATE \
-e DOCKER_CERT_PATH \
-e DOCKER_HOST \
-e DOCKER_MACHINE_NAME \
-e DOCKER_TLS_VERIFY \
-e NO_PROXY \
-e VAULT_TEST_LOG_DIR=<< parameters.log_dir >> \
--network vaulttest --name \
testcontainer << parameters.go_image >> \
......@@ -116,19 +128,27 @@ steps:
docker cp . testcontainer:/go/src/github.com/hashicorp/vault/
docker cp $DOCKER_CERT_PATH/ testcontainer:$DOCKER_CERT_PATH
# Copy the downloaded modules inside the container.
docker exec testcontainer sh -c 'mkdir -p /go/pkg'
docker cp "$(go env GOPATH)/pkg/mod" testcontainer:/go/pkg/mod
docker exec -w /go/src/github.com/hashicorp/vault/ \
-e CIRCLECI -e GOCACHE=/tmp/gocache -e VAULT_CI_GO_TEST_RACE \
-e CIRCLECI -e VAULT_CI_GO_TEST_RACE \
-e GOCACHE=/tmp/gocache \
-e GO_TAGS \
-e GOPROXY="off" \
-e VAULT_LICENSE_CI \
-e GOARCH=<< parameters.arch >> \
testcontainer \
gotestsum --format=short-verbose \
--junitfile test-results/go-test/results.xml \
--jsonfile test-results/go-test/results.json \
-- \
gotestsum --format=short-verbose \
--junitfile test-results/go-test/results.xml \
--jsonfile test-results/go-test/results.json \
-- \
-tags "${GO_TAGS} ${EXTRA_TAGS}" \
-timeout=60m \
-parallel=20 \
<< parameters.extra_flags >> \
${package_names}
-timeout=60m \
-parallel=20 \
<< parameters.extra_flags >> \
${package_names}
else
GOARCH=<< parameters.arch >> \
GOCACHE=<< parameters.cache_dir >> \
......
---
description: >
Ensure the right version of Go is installed and set GOPATH to $HOME/go.
parameters:
GOPROXY:
description: >
Set GOPROXY. By default this is set to "off" meaning you have to have all modules pre-downloaded.
type: string
default: "off"
GOPRIVATE:
description: Set GOPRIVATE, defaults to github.com/hashicorp/*
type: string
default: github.com/hashicorp/*
steps:
- run:
working_directory: ~/
......@@ -16,6 +26,8 @@ steps:
mkdir $GOPATH 2>/dev/null || { sudo mkdir $GOPATH && sudo chmod 777 $GOPATH; }
echo "export GOPATH='$GOPATH'" >> "$BASH_ENV"
echo "export PATH='$PATH:$GOPATH/bin:/usr/local/go/bin'" >> "$BASH_ENV"
echo "export GOPROXY=<<parameters.GOPROXY>>" >> "$BASH_ENV"
echo "export GOPRIVATE=<<parameters.GOPRIVATE>>" >> "$BASH_ENV"
echo "$ go version"
go version
......@@ -2,6 +2,7 @@ executor: go-machine
steps:
- setup-go
- checkout
- restore_go_mod_cache
- attach_workspace:
at: .
- run:
......
description: Ensures nothing obvious is broken for faster failures.
docker:
- image: circleci/buildpack-deps
shell: /usr/bin/env bash -euo pipefail
environment:
CCI_VERSION: 0.1.5691
description: Ensure nothing obvious is broken, and pre-cache Go modules.
executor: go-machine
steps:
# Setup Go enabling the proxy for downloading modules.
- setup-go:
GOPROXY: https://proxy.golang.org,direct
- checkout
- run:
name: Install CircleCI CLI
......@@ -12,10 +11,10 @@ steps:
ARCH: linux_amd64
BASE: https://github.com/CircleCI-Public/circleci-cli/releases/download
command: |
export CCI_PATH=/tmp/circleci-cli/$CCI_VERSION
export CCI_PATH=/tmp/circleci-cli/$CIRCLECI_CLI_VERSION
mkdir -p $CCI_PATH
NAME=circleci-cli_${CCI_VERSION}_${ARCH}
URL=$BASE/v${CCI_VERSION}/${NAME}.tar.gz
NAME=circleci-cli_${CIRCLECI_CLI_VERSION}_${ARCH}
URL=$BASE/v${CIRCLECI_CLI_VERSION}/${NAME}.tar.gz
curl -sSL $URL \
| tar --overwrite --strip-components=1 -xz -C $CCI_PATH "${NAME}/circleci"
# Add circleci to the path for subsequent steps.
......@@ -26,3 +25,5 @@ steps:
which circleci
circleci version
- run: make ci-verify
- configure-git
- refresh_go_mod_cache
......@@ -2,6 +2,7 @@ executor: go-machine
steps:
- setup-go
- checkout
- restore_go_mod_cache
- go_test:
log_dir: "/tmp/testlogs"
save_cache: true
......
......@@ -24,24 +24,24 @@ quickly merge or address your contributions.
### Reporting an Issue
* Make sure you test against the latest released version. It is possible
we already fixed the bug you're experiencing. Even better is if you can test
against `master`, as bugs are fixed regularly but new versions are only
released every few months.
* Make sure you test against the latest released version. It is possible we
already fixed the bug you're experiencing. Even better is if you can test
against the `main` branch, as the bugs are regularly fixed but new versions
are only released every few months.
* Provide steps to reproduce the issue, and if possible include the expected
results as well as the actual results. Please provide text, not screen shots!
* If you are seeing an internal Vault error (a status code of 5xx), please be
sure to post relevant parts of (or the entire) Vault log, as often these
errors are logged on the server but not reported to the user
errors are logged on the server but not reported to the user.
* If you experienced a panic, please create a [gist](https://gist.github.com)
of the *entire* generated crash log for us to look at. Double check
no sensitive items were in the log.
* Respond as promptly as possible to any questions made by the Vault
team to your issue. Stale issues will be closed periodically.
team to your issue.
### Issue Lifecycle
......@@ -58,9 +58,14 @@ quickly merge or address your contributions.
referenced in the commit message so that the code that fixes it is clearly
linked.
5. The issue is closed. Sometimes, valid issues will be closed to keep
the issue tracker clean. The issue is still indexed and available for
future viewers, or can be re-opened if necessary.
5. The issue is closed.
6. Issues that are not reproducible and/or not gotten responses for a long time are
stale issues. In order to provide faster responses and better engagement with
the community, we strive to keep the issue tracker clean and the issue count
low. In this regard, our current policy is to close stale issues after 30 days.
Closed issues will still be indexed and available for future viewers. If users
feel that the issue is still relevant, we encourage reopening them.
## Pull requests
......
......@@ -228,6 +228,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
......@@ -237,6 +238,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
......@@ -246,6 +248,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
......@@ -292,6 +295,8 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
......
......@@ -887,9 +887,11 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
switch tokenTypeRaw.(string) {
case "default-service":
data.Raw["token_type"] = "service"
resp = &logical.Response{}
resp.AddWarning("default-service has no useful meaning; adjusting to service")
case "default-batch":
data.Raw["token_type"] = "batch"
resp = &logical.Response{}
resp.AddWarning("default-batch has no useful meaning; adjusting to batch")
}
}
......@@ -976,7 +978,9 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
}
if role.TokenMaxTTL > b.System().MaxLeaseTTL() {
resp = &logical.Response{}
if resp == nil {
resp = &logical.Response{}
}
resp.AddWarning("token_max_ttl is greater than the backend mount's maximum TTL value; issued tokens' max TTL value will be truncated")
}
......
......@@ -209,6 +209,9 @@ func TestAppRole_LocalSecretIDImmutability(t *testing.T) {
Storage: storage,
Data: roleData,
})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected an error since local_secret_ids can't be overwritten")
}
......@@ -1820,6 +1823,136 @@ func TestAppRole_RoleWithTokenBoundCIDRsCRUD(t *testing.T) {
}
}
func TestAppRole_RoleWithTokenTypeCRUD(t *testing.T) {
var resp *logical.Response
var err error
b, storage := createBackendWithStorage(t)
roleData := map[string]interface{}{
"policies": "p,q,r,s",
"secret_id_num_uses": 10,
"secret_id_ttl": 300,
"token_ttl": 400,
"token_max_ttl": 500,
"token_num_uses": 600,
"token_type": "default-service",
}
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)
}
if 0 == len(resp.Warnings) {
t.Fatalf("bad:\nexpected warning in resp:%#v\n", resp.Warnings)
}
roleReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
expected := map[string]interface{}{
"bind_secret_id": true,
"policies": []string{"p", "q", "r", "s"},
"secret_id_num_uses": 10,
"secret_id_ttl": 300,
"token_ttl": 400,
"token_max_ttl": 500,
"token_num_uses": 600,
"token_type": "service",
}
var expectedStruct roleStorageEntry
err = mapstructure.Decode(expected, &expectedStruct)
if err != nil {
t.Fatal(err)
}
var actualStruct roleStorageEntry
err = mapstructure.Decode(resp.Data, &actualStruct)
if err != nil {
t.Fatal(err)
}
expectedStruct.RoleID = actualStruct.RoleID
if !reflect.DeepEqual(expectedStruct, actualStruct) {
t.Fatalf("bad:\nexpected:%#v\nactual:%#v\n", expectedStruct, actualStruct)
}
roleData = map[string]interface{}{
"role_id": "test_role_id",
"policies": "a,b,c,d",
"secret_id_num_uses": 100,
"secret_id_ttl": 3000,
"token_ttl": 4000,
"token_max_ttl": 5000,
"token_type": "default-service",
}
roleReq.Data = roleData
roleReq.Operation = logical.UpdateOperation
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if 0 == len(resp.Warnings) {
t.Fatalf("bad:\nexpected a warning in resp:%#v\n", resp.Warnings)
}
roleReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
expected = map[string]interface{}{
"policies": []string{"a", "b", "c", "d"},
"secret_id_num_uses": 100,
"secret_id_ttl": 3000,
"token_ttl": 4000,
"token_max_ttl": 5000,
"token_type": "service",
}
err = mapstructure.Decode(expected, &expectedStruct)
if err != nil {
t.Fatal(err)
}
err = mapstructure.Decode(resp.Data, &actualStruct)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expectedStruct, actualStruct) {
t.Fatalf("bad:\nexpected:%#v\nactual:%#v\n", expectedStruct, actualStruct)
}
// Delete test for role
roleReq.Path = "role/role1"
roleReq.Operation = logical.DeleteOperation
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
roleReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
}
func createRole(t *testing.T, b *backend, s logical.Storage, roleName, policies string) {
roleData := map[string]interface{}{
"policies": policies,
......
......@@ -44,21 +44,26 @@ func TestAppRole_TidyDanglingAccessors_Normal(t *testing.T) {
SecretIDHMAC: "samplesecretidhmac",
},
)
err = storage.Put(context.Background(), entry1)
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",
},
)
err = storage.Put(context.Background(), entry2)
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 {
......@@ -138,11 +143,14 @@ func TestAppRole_TidyDanglingAccessors_RaceTest(t *testing.T) {
SecretIDHMAC: "samplesecretidhmac",
},
)
err = storage.Put(context.Background(), entry)
if err != nil {
t.Fatal(err)
}
if err := storage.Put(context.Background(), entry); err != nil {
t.Fatal(err)
}
count++
time.Sleep(100 * time.Microsecond)
}
......
......@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/template"
"github.com/hashicorp/vault/sdk/logical"
rabbithole "github.com/michaelklishin/rabbit-hole"
)
......@@ -38,6 +39,10 @@ func pathConfigConnection(b *backend) *framework.Path {
Type: framework.TypeString,
Description: "Name of the password policy to use to generate passwords for dynamic credentials.",
},
"username_template": {
Type: framework.TypeString,
Description: "Template describing how dynamic usernames are generated.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
......@@ -65,6 +70,19 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
return logical.ErrorResponse("missing password"), nil
}
usernameTemplate := data.Get("username_template").(string)
if usernameTemplate != "" {
up, err := template.NewTemplate(template.Template(usernameTemplate))
if err != nil {
return logical.ErrorResponse("unable to initialize username template: %w", err), nil
}
_, err = up.Generate(UsernameMetadata{})
if err != nil {
return logical.ErrorResponse("invalid username template: %w", err), nil
}
}
passwordPolicy := data.Get("password_policy").(string)
// Don't check the connection_url if verification is disabled
......@@ -84,10 +102,11 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
// Store it
config := connectionConfig{
URI: uri,
Username: username,
Password: password,
PasswordPolicy: passwordPolicy,
URI: uri,
Username: username,
Password: password,
PasswordPolicy: passwordPolicy,
UsernameTemplate: usernameTemplate,
}
err := writeConfig(ctx, req.Storage, config)
if err != nil {
......@@ -140,6 +159,9 @@ type connectionConfig struct {
// PasswordPolicy for generating passwords for dynamic credentials
PasswordPolicy string `json:"password_policy"`
// UsernameTemplate for storing the raw template in Vault's backing data store
UsernameTemplate string `json:"username_template"`
}
const pathConfigConnectionHelpSyn = `
......
package rabbitmq
import (
"context"
"reflect"
"testing"
"github.com/hashicorp/vault/sdk/logical"
)
func TestBackend_ConfigConnection_DefaultUsernameTemplate(t *testing.T) {
var resp *logical.Response
var err error
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b := Backend()
if err = b.Setup(context.Background(), config); err != nil {
t.Fatal(err)
}
configData := map[string]interface{}{
"connection_uri": "uri",
"username": "username",
"password": "password",
"verify_connection": "false",
}
configReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/connection",
Storage: config.StorageView,
Data: configData,
}
resp, err = b.HandleRequest(context.Background(), configReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp != nil {
t.Fatal("expected a nil response")
}
actualConfig, err := readConfig(context.Background(), config.StorageView)
if err != nil {
t.Fatalf("unable to read configuration: %v", err)
}
expectedConfig := connectionConfig{
URI: "uri",
Username: "username",
Password: "password",
UsernameTemplate: "",
}
if !reflect.DeepEqual(actualConfig, expectedConfig) {
t.Fatalf("Expected: %#v\nActual: %#v", expectedConfig, actualConfig)
}
}
func TestBackend_ConfigConnection_CustomUsernameTemplate(t *testing.T) {
var resp *logical.Response
var err error
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b := Backend()
if err = b.Setup(context.Background(), config); err != nil {
t.Fatal(err)
}
configData := map[string]interface{}{
"connection_uri": "uri",
"username": "username",
"password": "password",
"verify_connection": "false",
"username_template": "{{ .DisplayName }}",
}
configReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/connection",
Storage: config.StorageView,
Data: configData,
}
resp, err = b.HandleRequest(context.Background(), configReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp != nil {
t.Fatal("expected a nil response")
}
actualConfig, err := readConfig(context.Background(), config.StorageView)
if err != nil {
t.Fatalf("unable to read configuration: %v", err)
}
expectedConfig := connectionConfig{
URI: "uri",
Username: "username",
Password: "password",
UsernameTemplate: "{{ .DisplayName }}",
}
if !reflect.DeepEqual(actualConfig, expectedConfig) {
t.Fatalf("Expected: %#v\nActual: %#v", expectedConfig, actualConfig)
}
}
......@@ -5,12 +5,16 @@ import (
"fmt"
"io/ioutil"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/template"
"github.com/hashicorp/vault/sdk/logical"
rabbithole "github.com/michaelklishin/rabbit-hole"
)
const (
defaultUserNameTemplate = `{{ printf "%s-%s" (.DisplayName) (uuid) }}`
)
func pathCreds(b *backend) *framework.Path {
return &framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("name"),
......@@ -46,17 +50,31 @@ func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *fr
return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", name)), nil
}
// Ensure username is unique
uuidVal, err := uuid.GenerateUUID()
config, err := readConfig(ctx, req.Storage)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to read configuration: %w", err)
}
username := fmt.Sprintf("%s-%s", req.DisplayName, uuidVal)
config, err := readConfig(ctx, req.Storage)
usernameTemplate := config.UsernameTemplate
if usernameTemplate == "" {
usernameTemplate = defaultUserNameTemplate
}
up, err := template.NewTemplate(template.Template(usernameTemplate))
if err != nil {
return nil, fmt.Errorf("unable to read configuration: %w", err)
return nil, fmt.Errorf("unable to initialize username template: %w", err)
}
um := UsernameMetadata{
DisplayName: req.DisplayName,
RoleName: name,
}
username, err := up.Generate(um)
if err != nil {
return nil, fmt.Errorf("failed to generate username: %w", err)
}
fmt.Printf("username: %s\n", username)
password, err := b.generatePassword(ctx, config.PasswordPolicy)
if err != nil {
......@@ -189,6 +207,12 @@ func isIn200s(respStatus int) bool {
return respStatus >= 200 && respStatus < 300
}
// UsernameMetadata is metadata the database plugin can use to generate a username
type UsernameMetadata struct {
DisplayName string
RoleName string
}
const pathRoleCreateReadHelpSyn = `
Request RabbitMQ credentials for a certain role.
`
......
package rabbitmq
import (
"context"
"testing"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
func TestBackend_RoleCreate_DefaultUsernameTemplate(t *testing.T) {
cleanup, connectionURI := prepareRabbitMQTestContainer(t)
defer cleanup()
var resp *logical.Response
var err error
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b := Backend()
if err = b.Setup(context.Background(), config); err != nil {
t.Fatal(err)
}
configData := map[string]interface{}{
"connection_uri": connectionURI,
"username": "guest",
"password": "guest",
"username_template": "",
}
configReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/connection",
Storage: config.StorageView,
Data: configData,
}
resp, err = b.HandleRequest(context.Background(), configReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp != nil {
t.Fatal("expected a nil response")
}
roleData := map[string]interface{}{
"name": "foo",
"tags": "bar",
}
roleReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/foo",
Storage: config.StorageView,
Data: roleData,
}
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp != nil {
t.Fatal("expected a nil response")
}
credsReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/foo",
Storage: config.StorageView,
DisplayName: "token",
}
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp == nil {
t.Fatal("missing creds response")
}
if resp.Data == nil {
t.Fatalf("missing creds data")
}
username, exists := resp.Data["username"]
if !exists {
t.Fatalf("missing username in response")
}
require.Regexp(t, `^token-[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$`, username)
}
func TestBackend_RoleCreate_CustomUsernameTemplate(t *testing.T) {
cleanup, connectionURI := prepareRabbitMQTestContainer(t)
defer cleanup()
var resp *logical.Response
var err error
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b := Backend()
if err = b.Setup(context.Background(), config); err != nil {
t.Fatal(err)
}
configData := map[string]interface{}{
"connection_uri": connectionURI,
"username": "guest",
"password": "guest",
"username_template": "foo-{{ .DisplayName }}",
}
configReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/connection",
Storage: config.StorageView,
Data: configData,
}
resp, err = b.HandleRequest(context.Background(), configReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp != nil {
t.Fatal("expected a nil response")
}
roleData := map[string]interface{}{
"name": "foo",
"tags": "bar",
}
roleReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/foo",
Storage: config.StorageView,
Data: roleData,
}
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp != nil {
t.Fatal("expected a nil response")
}
credsReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "creds/foo",
Storage: config.StorageView,
DisplayName: "token",
}
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr:%s", resp, err)
}
if resp == nil {
t.Fatal("missing creds response")
}
if resp.Data == nil {
t.Fatalf("missing creds data")
}
username, exists := resp.Data["username"]
if !exists {
t.Fatalf("missing username in response")
}
require.Regexp(t, `^foo-token$`, username)
}
```release-note:improvement
agent: Allow Agent auto auth to read symlinked JWT files
```
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment