diff --git a/Makefile b/Makefile index 9d38336..9159411 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ all: build lint test .PHONY: build build: vault.hcl + go install ./... vault.hcl: vault.hcl.in sed -e 's;@@GOBIN@@;$(GOBIN);g' < $< > $@ diff --git a/README.md b/README.md index 36f3791..a0324de 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,17 @@ make dev vault secrets enable -path=splunk -plugin-name=vault-plugin-splunk plugin || true vault write splunk/config/local url="${SPLUNK_ADDR}" insecure_tls=true username=admin password="${SPLUNK_PASSWORD}" allowed_roles='*' vault write splunk/roles/local-admin roles=admin email='test@example.com' connection=local default_ttl=30s max_ttl=5m +vault read splunk/roles/local-admin +Key Value +--- ----- +connection local +default_app n/a +default_ttl 30s +email test@example.com +max_ttl 5m +roles [admin] +tz n/a +user_prefix vault ``` ## Plugin Usage @@ -82,9 +93,9 @@ Create temporary admin account: password 439e831b-e395-9999-2cd7-856381db3394 roles [admin] url https://localhost:8089 - username vault_local-admin_okta-mweber_70c6c140-238d-e12b-3289-8e38f8c4d9f5_1553712516020311000 + username vault_70c6c140-238d-e12b-3289-8e38f8c4d9f5 -This creates a new user account `vault_local-admin_okta-mweber_70c6...` +This creates a new user account `vault_70c6c140-238d-e12b-3289-8e38f8c4d9f5` with a new random password. The account was configured to have the admin role. It will automatically be queued for deletion by vault after the configured lease ends, in 5 minutes. We can use `vault diff --git a/backend_test.go b/backend_test.go index b8ec46f..44304b3 100644 --- a/backend_test.go +++ b/backend_test.go @@ -23,6 +23,7 @@ func TestBackend_basic(t *testing.T) { roleConfig := roleConfig{ Connection: "testconn", Roles: []string{"admin"}, + UserPrefix: defaultUserPrefix, } logicaltest.Test(t, logicaltest.TestCase{ @@ -85,22 +86,34 @@ func TestBackend_RoleCRUD(t *testing.T) { t.Fatal(err) } - roleConfig := roleConfig{ + testRoleConfig := roleConfig{ Connection: "testconn", Roles: []string{"admin"}, + UserPrefix: "my-custom-prefix", } logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepConfig(t), - testAccStepRole(t, "test", roleConfig), + testAccStepRole(t, "test", testRoleConfig), testAccStepRoleMissingRoleName(t), testAccStepRoleMissingRoles(t, "MISSING"), - testAccStepRoleRead(t, "test", roleConfig), + testAccStepRoleRead(t, "test", testRoleConfig), testAccStepRoleDelete(t, "test"), }, }) + emptyUserPrefixConfig := roleConfig{ + Connection: "testconn", + Roles: []string{"admin"}, + UserPrefix: "", + } + logicaltest.Test(t, logicaltest.TestCase{ + LogicalBackend: b, + Steps: []logicaltest.TestStep{ + testEmptyUserPrefix(t, "test", emptyUserPrefixConfig), + }, + }) } // Test steps @@ -188,6 +201,22 @@ func testAccStepRoleMissingRoleName(t *testing.T) logicaltest.TestStep { } } +func testEmptyUserPrefix(t *testing.T, role string, config roleConfig) logicaltest.TestStep { + return logicaltest.TestStep{ + Operation: logical.CreateOperation, + Path: rolesPrefix + role, + Data: config.toResponseData(), + ErrorOk: true, + Check: func(resp *logical.Response) error { + if resp == nil { + return fmt.Errorf("response is nil") + } + assert.Error(t, resp.Error(), "user_prefix can't be set to empty string") + return nil + }, + } +} + func testAccStepCredsRead(t *testing.T, role string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, diff --git a/path_creds_create.go b/path_creds_create.go index 2181aef..e329267 100644 --- a/path_creds_create.go +++ b/path_creds_create.go @@ -3,8 +3,6 @@ package splunk import ( "context" "fmt" - "time" - "github.com/hashicorp/errwrap" "github.com/hashicorp/go-uuid" "github.com/hashicorp/vault/helper/strutil" @@ -62,7 +60,11 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d if err != nil { return nil, err } - username := fmt.Sprintf("vault_%s_%s_%s_%d", name, req.DisplayName, userUUID, time.Now().UnixNano()) + userPrefix := role.UserPrefix + if role.UserPrefix == defaultUserPrefix { + userPrefix = fmt.Sprintf("%s_%s", role.UserPrefix, req.DisplayName) + } + username := fmt.Sprintf("%s_%s", userPrefix, userUUID) passwd, err := uuid.GenerateUUID() if err != nil { return nil, errwrap.Wrapf("error generating new password {{err}}", err) diff --git a/path_roles.go b/path_roles.go index b4a4ecb..10af065 100644 --- a/path_roles.go +++ b/path_roles.go @@ -9,6 +9,7 @@ import ( ) const rolesPrefix = "roles/" +const defaultUserPrefix = "vault" func (b *backend) pathRoles() *framework.Path { return &framework.Path{ @@ -47,6 +48,11 @@ func (b *backend) pathRoles() *framework.Path { Type: framework.TypeString, Description: "User time zone.", }, + "user_prefix": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Prefix for creating new users", + Default: defaultUserPrefix, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: b.rolesReadHandler, @@ -124,6 +130,12 @@ func (b *backend) rolesWriteHandler(ctx context.Context, req *logical.Request, d if tzRaw, ok := getValue(data, req.Operation, "tz"); ok { role.TZ = tzRaw.(string) } + if userPrefixRaw, ok := getValue(data, req.Operation, "user_prefix"); ok { + role.UserPrefix = userPrefixRaw.(string) + } + if role.UserPrefix == "" { + return logical.ErrorResponse("user_prefix can't be set to empty string"), nil + } if err := role.store(ctx, req.Storage, name); err != nil { return nil, err diff --git a/role.go b/role.go index 3af861f..2eab11c 100644 --- a/role.go +++ b/role.go @@ -20,6 +20,7 @@ type roleConfig struct { DefaultApp string `json:"default_app,omitempty" structs:"default_app"` Email string `json:"email,omitempty" structs:"email"` TZ string `json:"tz,omitempty" structs:"tz"` + UserPrefix string `json:"user_prefix,omitempty" structs:"user_prefix"` } // Role returns nil if role named `name` does not exist in `storage`, otherwise