Skip to content
This repository was archived by the owner on Mar 15, 2024. It is now read-only.

Shorten generated usernames, add option user_id_scheme for roles #27

Merged
merged 1 commit into from
Mar 27, 2020
Merged
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
73 changes: 54 additions & 19 deletions backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,29 @@ func TestBackend_basic(t *testing.T) {
t.Fatal(err)
}

roleConfig := roleConfig{
Connection: "testconn",
Roles: []string{"admin"},
UserPrefix: defaultUserPrefix,
schemes := []string{
userIDSchemeUUID4_v0_5_0,
userIDSchemeUUID4,
userIDSchemeBase58_64,
userIDSchemeBase58_128,
}
for _, scheme := range schemes {
roleConfig := roleConfig{
Connection: "testconn",
Roles: []string{"admin"},
UserPrefix: defaultUserPrefix,
UserIDScheme: scheme,
}

logicaltest.Test(t, logicaltest.TestCase{
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t),
testAccStepRole(t, "test", roleConfig),
testAccStepCredsRead(t, "test"),
},
})
logicaltest.Test(t, logicaltest.TestCase{
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t),
testAccStepRole(t, "test", roleConfig),
testAccStepCredsRead(t, "test"),
},
})
}
}

func TestBackend_RotateRoot(t *testing.T) {
Expand Down Expand Up @@ -92,6 +101,7 @@ func TestBackend_RoleCRUD(t *testing.T) {
AllowedServerRoles: []string{"*"},
PasswordSpec: DefaultPasswordSpec(),
UserPrefix: "my-custom-prefix",
UserIDScheme: userIDSchemeUUID4,
}

logicaltest.Test(t, logicaltest.TestCase{
Expand All @@ -105,17 +115,23 @@ func TestBackend_RoleCRUD(t *testing.T) {
testAccStepRoleDelete(t, "test"),
},
})
emptyUserPrefixConfig := roleConfig{
Connection: "testconn",
Roles: []string{"admin"},
UserPrefix: "",
}
emptyUserPrefixConfig := testRoleConfig
emptyUserPrefixConfig.UserPrefix = ""
logicaltest.Test(t, logicaltest.TestCase{
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testEmptyUserPrefix(t, "test", emptyUserPrefixConfig),
},
})

userIDSchemeConfig := testRoleConfig
userIDSchemeConfig.UserIDScheme = "-invalid-"
logicaltest.Test(t, logicaltest.TestCase{
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testUserIDScheme(t, "test", "-invalid-", userIDSchemeConfig),
},
})
}

// Test steps
Expand Down Expand Up @@ -219,6 +235,22 @@ func testEmptyUserPrefix(t *testing.T, role string, config roleConfig) logicalte
}
}

func testUserIDScheme(t *testing.T, role, idScheme 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(), fmt.Sprintf("invalid user_id_scheme: %q", idScheme))
return nil
},
}
}

func testAccStepCredsRead(t *testing.T, role string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Expand All @@ -235,8 +267,11 @@ func testAccStepCredsRead(t *testing.T, role string) logicaltest.TestStep {
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
t.Logf("[WARN] Generated credentials: %+v", d)
// XXXX check that generated user can login
// check that generated user can login
conn := splunk.NewTestSplunkClient(d.URL, d.Username, d.Password)
_, _, err := conn.Introspection.ServerInfo()
assert.NilError(t, err)

// XXXX check that generated user is deleted if lease expires
return nil
},
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ require (
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.1.2
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/mr-tron/base58 v1.1.3
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v0.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
Expand Down
13 changes: 12 additions & 1 deletion path_creds_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,18 @@ func (b *backend) credsReadHandler(ctx context.Context, req *logical.Request, d
}

func generateUserID(roleConfig *roleConfig) (string, error) {
return uuid.GenerateUUID()
switch roleConfig.UserIDScheme {
case userIDSchemeUUID4_v0_5_0:
fallthrough
case userIDSchemeUUID4:
return uuid.GenerateUUID()
case userIDSchemeBase58_64:
return GenerateShortUUID(8)
case userIDSchemeBase58_128:
return GenerateShortUUID(16)
default:
return "", fmt.Errorf("invalid user_id_scheme: %q", roleConfig.UserIDScheme)
}
}

func generateUserPassword(roleConfig *roleConfig) (string, error) {
Expand Down
32 changes: 29 additions & 3 deletions path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ package splunk

import (
"context"
"fmt"
"time"

"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)

const rolesPrefix = "roles/"
const defaultUserPrefix = "vault"
const (
rolesPrefix = "roles/"
defaultUserPrefix = "vault"

userIDSchemeUUID4_v0_5_0 = ""
userIDSchemeUUID4 = "uuid4"
userIDSchemeBase58_64 = "base58-64"
userIDSchemeBase58_128 = "base58-128"
)

func (b *backend) pathRoles() *framework.Path {
return &framework.Path{
Expand Down Expand Up @@ -58,9 +66,15 @@ func (b *backend) pathRoles() *framework.Path {
},
"user_prefix": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Prefix for creating new users",
Description: "Prefix for creating new users.",
Default: defaultUserPrefix,
},
"user_id_scheme": &framework.FieldSchema{
Type: framework.TypeLowerCaseString,
Description: fmt.Sprintf("ID generation scheme (%s, %s, %s). Default: %s",
userIDSchemeUUID4, userIDSchemeBase58_64, userIDSchemeBase58_128, userIDSchemeBase58_64),
Default: userIDSchemeBase58_64,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.rolesReadHandler,
Expand Down Expand Up @@ -149,6 +163,18 @@ func (b *backend) rolesWriteHandler(ctx context.Context, req *logical.Request, d
return logical.ErrorResponse("user_prefix can't be set to empty string"), nil
}

if userIDSchemeRaw, ok := getValue(data, req.Operation, "user_id_scheme"); ok {
role.UserIDScheme = userIDSchemeRaw.(string)
}
switch role.UserIDScheme {
case userIDSchemeUUID4_v0_5_0:
case userIDSchemeUUID4:
case userIDSchemeBase58_64:
case userIDSchemeBase58_128:
default:
return logical.ErrorResponse("invalid user_id_scheme: %q", role.UserIDScheme), nil
}

if err := role.store(ctx, req.Storage, name); err != nil {
return nil, err
}
Expand Down
11 changes: 6 additions & 5 deletions role.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ type roleConfig struct {
PasswordSpec *PasswordSpec `json:"password_spec" structs:"password_spec"`

// Splunk user attributes
Roles []string `json:"roles" structs:"roles"`
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"`
Roles []string `json:"roles" structs:"roles"`
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"`
UserIDScheme string `json:"user_id_scheme,omitempty" structs:"user_id_scheme"`
}

// Role returns nil if role named `name` does not exist in `storage`, otherwise
Expand Down
18 changes: 18 additions & 0 deletions uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package splunk

import (
"github.com/hashicorp/go-uuid"
"github.com/mr-tron/base58"
)

func GenerateShortUUID(size int) (string, error) {
bytes, err := uuid.GenerateRandomBytes(size)
if err != nil {
return "", err
}
return FormatShortUUID(bytes), nil
}

func FormatShortUUID(bytes []byte) string {
return base58.Encode(bytes)
}
60 changes: 60 additions & 0 deletions uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package splunk

import (
"fmt"
"testing"

"github.com/mr-tron/base58"
"gotest.tools/assert"
)

func TestGenerateShortUUID(t *testing.T) {
for _, size := range []int{8, 16} {
uuid, err := GenerateShortUUID(size)
assert.NilError(t, err)
fmt.Println(uuid)
bytes, err := base58.Decode(uuid)
assert.NilError(t, err)
assert.Equal(t, size, len(bytes))
}
}

func TestFormatShortUUID(t *testing.T) {
type args struct {
bytes []byte
}
tests := []struct {
name string
args args
want string
}{
{
name: "0_x_8",
args: args{[]byte{0, 0, 0, 0, 0, 0, 0, 0}},
want: "11111111",
},
{
name: "1_x_8",
args: args{[]byte{0, 0, 0, 0, 0, 0, 0, 1}},
want: "11111112",
},
{
name: "255_x_8",
args: args{[]byte{255, 255, 255, 255, 255, 255, 255, 255}},
want: "jpXCZedGfVQ",
},
{
name: "uuid4",
args: args{[]byte{0xb3, 0x3b, 0x6b, 0x76, 0xcb, 0x1f, 0xbe, 0x28, 0xbd, 0x5b, 0x86, 0xca, 0x76, 0x23, 0x72, 0x72}},
want: "P8gD5AcMf2n6FkGz9nydEZ",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := FormatShortUUID(tt.args.bytes); got != tt.want {
t.Errorf("FormatShortUUID() = %v, want %v", got, tt.want)
}
})
}
}