Skip to content

Commit

Permalink
Detailed static roles + error messages fix (#126)
Browse files Browse the repository at this point in the history
Static roles: detailed openstack domain/project settings + error messages fix

Added user_domain_id/name and project_domain_id/name parameters for detailed static roles management.
Several error messages fixed.
Acceptance tests
vault-plugin-secrets-openstack % make functional
Running acceptance tests...
=== RUN   TestPlugin
=== RUN   TestPlugin/TestCloudLifecycle
=== RUN   TestPlugin/TestCloudLifecycle/WriteCloud
=== RUN   TestPlugin/TestCloudLifecycle/ReadCloud
=== RUN   TestPlugin/TestCloudLifecycle/ListClouds
=== RUN   TestPlugin/TestCloudLifecycle/ListClouds/method-LIST
=== PAUSE TestPlugin/TestCloudLifecycle/ListClouds/method-LIST
=== RUN   TestPlugin/TestCloudLifecycle/ListClouds/method-GET
=== PAUSE TestPlugin/TestCloudLifecycle/ListClouds/method-GET
=== CONT  TestPlugin/TestCloudLifecycle/ListClouds/method-LIST
=== CONT  TestPlugin/TestCloudLifecycle/ListClouds/method-GET
=== RUN   TestPlugin/TestCloudLifecycle/DeleteCloud
=== RUN   TestPlugin/TestCredsLifecycle
=== RUN   TestPlugin/TestCredsLifecycle/user_domain_id_token
=== RUN   TestPlugin/TestCredsLifecycle/root_token
=== RUN   TestPlugin/TestCredsLifecycle/user_token
=== RUN   TestPlugin/TestCredsLifecycle/user_password
=== RUN   TestPlugin/TestInfo
info_test.go:42:
Error Trace:    info_test.go:42
Error:          Should NOT be empty, but was &{    }
Test:           TestPlugin/TestInfo
=== RUN   TestPlugin/TestRoleLifecycle
=== RUN   TestPlugin/TestRoleLifecycle/WriteRole
=== RUN   TestPlugin/TestRoleLifecycle/ReadRole
=== RUN   TestPlugin/TestRoleLifecycle/ListRoles
=== RUN   TestPlugin/TestRoleLifecycle/ListRoles/method-LIST
=== PAUSE TestPlugin/TestRoleLifecycle/ListRoles/method-LIST
=== RUN   TestPlugin/TestRoleLifecycle/ListRoles/method-GET
=== PAUSE TestPlugin/TestRoleLifecycle/ListRoles/method-GET
=== CONT  TestPlugin/TestRoleLifecycle/ListRoles/method-LIST
=== CONT  TestPlugin/TestRoleLifecycle/ListRoles/method-GET
=== RUN   TestPlugin/TestRoleLifecycle/DeleteRole
=== RUN   TestPlugin/TestRootRotate
rotate_test.go:65: Cloud with name default1 was created
rotate_test.go:68: Cloud with name 4bju was created
plugin_test.go:337: Cloud with name 4bju has been removed
plugin_test.go:337: Cloud with name default1 has been removed
=== RUN   TestPlugin/TestStaticCredsLifecycle
=== RUN   TestPlugin/TestStaticCredsLifecycle/user_password
=== RUN   TestPlugin/TestStaticCredsLifecycle/user_token_project_id
=== RUN   TestPlugin/TestStaticCredsLifecycle/user_token_project_name
=== RUN   TestPlugin/TestStaticCredsLifecycle/user_domain_id_token
=== RUN   TestPlugin/TestStaticRoleLifecycle
=== RUN   TestPlugin/TestStaticRoleLifecycle/WriteRole
=== RUN   TestPlugin/TestStaticRoleLifecycle/ReadRole
=== RUN   TestPlugin/TestStaticRoleLifecycle/ListRoles
=== RUN   TestPlugin/TestStaticRoleLifecycle/ListRoles/method-LIST
=== PAUSE TestPlugin/TestStaticRoleLifecycle/ListRoles/method-LIST
=== RUN   TestPlugin/TestStaticRoleLifecycle/ListRoles/method-GET
=== PAUSE TestPlugin/TestStaticRoleLifecycle/ListRoles/method-GET
=== CONT  TestPlugin/TestStaticRoleLifecycle/ListRoles/method-LIST
=== CONT  TestPlugin/TestStaticRoleLifecycle/ListRoles/method-GET
=== RUN   TestPlugin/TestStaticRoleLifecycle/DeleteRole
--- FAIL: TestPlugin (32.71s)
--- PASS: TestPlugin/TestCloudLifecycle (0.04s)
--- PASS: TestPlugin/TestCloudLifecycle/WriteCloud (0.04s)
--- PASS: TestPlugin/TestCloudLifecycle/ReadCloud (0.00s)
--- PASS: TestPlugin/TestCloudLifecycle/ListClouds (0.00s)
--- PASS: TestPlugin/TestCloudLifecycle/ListClouds/method-LIST (0.00s)
--- PASS: TestPlugin/TestCloudLifecycle/ListClouds/method-GET (0.00s)
--- PASS: TestPlugin/TestCloudLifecycle/DeleteCloud (0.00s)
--- PASS: TestPlugin/TestCredsLifecycle (7.99s)
--- PASS: TestPlugin/TestCredsLifecycle/user_domain_id_token (2.96s)
--- PASS: TestPlugin/TestCredsLifecycle/root_token (0.83s)
--- PASS: TestPlugin/TestCredsLifecycle/user_token (2.38s)
--- PASS: TestPlugin/TestCredsLifecycle/user_password (0.97s)
--- FAIL: TestPlugin/TestInfo (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle (0.61s)
--- PASS: TestPlugin/TestRoleLifecycle/WriteRole (0.59s)
--- PASS: TestPlugin/TestRoleLifecycle/ReadRole (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/ListRoles (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/ListRoles/method-GET (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/ListRoles/method-LIST (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/DeleteRole (0.00s)
--- PASS: TestPlugin/TestRootRotate (4.72s)
--- PASS: TestPlugin/TestStaticCredsLifecycle (16.24s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_password (3.38s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_token_project_id (4.00s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_token_project_name (3.91s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_domain_id_token (3.92s)
--- PASS: TestPlugin/TestStaticRoleLifecycle (2.96s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/WriteRole (1.05s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ReadRole (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ListRoles (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ListRoles/method-LIST (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ListRoles/method-GET (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/DeleteRole (0.00s)
FAIL
FAIL    github.com/opentelekomcloud/vault-plugin-secrets-openstack/acceptance   33.138s
FAIL
make: *** [functional] Error 1

Reviewed-by: Aloento
Reviewed-by: Anton Sidelnikov
  • Loading branch information
artem-lifshits authored Feb 6, 2023
1 parent c29266b commit 8f63890
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 53 deletions.
4 changes: 1 addition & 3 deletions acceptance/creds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package acceptance
import (
"fmt"
"net/http"
"os"
"testing"

"github.com/opentelekomcloud/vault-plugin-secrets-openstack/openstack"
Expand All @@ -31,7 +30,6 @@ type testCase struct {
func (p *PluginTest) TestCredsLifecycle() {
t := p.T()

userDomainID := os.Getenv("USER_DOMAIN_ID")
cloud := openstackCloudConfig(t)
require.NotEmpty(t, cloud)

Expand Down Expand Up @@ -68,7 +66,7 @@ func (p *PluginTest) TestCredsLifecycle() {
"user_domain_id_token": {
Cloud: cloud.Name,
ProjectID: aux.ProjectID,
UserDomainID: userDomainID,
UserDomainID: aux.DomainID,
Root: false,
SecretType: "token",
UserRoles: []string{"member"},
Expand Down
40 changes: 26 additions & 14 deletions acceptance/static_creds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import (
)

type testStaticCase struct {
cloud string
projectID string
projectName string
domainID string
secretType string
username string
extensions map[string]interface{}
cloud string
projectID string
projectName string
domainID string
secretType string
username string
userDomainId string
extensions map[string]interface{}
}

func (p *PluginTest) TestStaticCredsLifecycle() {
Expand Down Expand Up @@ -70,6 +71,16 @@ func (p *PluginTest) TestStaticCredsLifecycle() {
"identity_api_version": "3",
},
},
"user_domain_id_token": {
cloud: cloud.Name,
projectID: aux.ProjectID,
username: "static-test-4",
userDomainId: aux.DomainID,
secretType: "token",
extensions: map[string]interface{}{
"identity_api_version": "3",
},
},
}

for name, data := range cases {
Expand Down Expand Up @@ -152,13 +163,14 @@ func staticRotateCredsURL(roleName string) string {

func cloudToStaticRoleMap(data testStaticCase) map[string]interface{} {
return fixtures.SanitizedMap(map[string]interface{}{
"cloud": data.cloud,
"project_id": data.projectID,
"project_name": data.projectName,
"domain_id": data.domainID,
"secret_type": data.secretType,
"username": data.username,
"extensions": data.extensions,
"cloud": data.cloud,
"project_id": data.projectID,
"project_name": data.projectName,
"user_domain_id": data.userDomainId,
"domain_id": data.domainID,
"secret_type": data.secretType,
"username": data.username,
"extensions": data.extensions,
})
}

Expand Down
18 changes: 15 additions & 3 deletions doc/source/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@ created. If the role exists, it will be updated with the new attributes.
`domain_id`.

- `user_domain_id` `(string: <optional>)` - Specifies domain where user will be created with given domain id.
Mutually exclusive with `user_project_name`.
Mutually exclusive with `user_domain_name`.

- `user_domain_name` `(string: <optional>)` - Specifies domain where user will be created with given domain name.
Mutually exclusive with `user_project_id`.
Mutually exclusive with `user_domain_id`.

- `project_domain_id` `(string: <optional>)` - Specifies domain for project-scoped role with given domain id.
If one of `project_id` / `project_name` is not set `project_domain_id` value is ignored.
Expand Down Expand Up @@ -472,7 +472,19 @@ created. If the role exists, it will be updated with the new attributes.
- `domain_name` `(string: <optional>)` - Create a domain-scoped role with given domain name. Mutually exclusive with
`domain_id`.

When none of `project_name` or `project_id` is set, created role will have a project scope.
- `user_domain_id` `(string: <optional>)` - Specifies domain id of existing user.
Mutually exclusive with `user_domain_name`.

- `user_domain_name` `(string: <optional>)` - Specifies domain name of existing user.
Mutually exclusive with `user_domain_id`.

- `project_domain_id` `(string: <optional>)` - Specifies domain for project-scoped role with given domain id.
If one of `project_id` / `project_name` is not set `project_domain_id` value is ignored.

- `project_domain_name` `(string: <optional>)` - Specifies domain for project-scoped role with given domain name.
If one of `project_id` / `project_name` is not set `project_domain_name` value is ignored.

When one of `project_name` or `project_id` is set, created static role will have a project scope.

- `extensions` `(list: [])` - A list of strings representing a key/value pair to be used as extensions to the cloud
configuration (e.g. `volume_api_version` or endpoint overrides). Format is a key and value
Expand Down
2 changes: 1 addition & 1 deletion openstack/path_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (b *backend) pathCloudCreateUpdate(ctx context.Context, r *logical.Request,
// validate template first
_, err := RandomTemporaryUsername(cloudConfig.UsernameTemplate, &roleEntry{})
if err != nil {
return logical.ErrorResponse("invalid username template: %s", err), nil
return logical.ErrorResponse("invalid username template: %w", err), nil
}
} else if r.Operation == logical.CreateOperation {
cloudConfig.UsernameTemplate = DefaultUsernameTemplate
Expand Down
6 changes: 6 additions & 0 deletions openstack/path_static_creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ func getScopeFromStaticRole(role *roleStaticEntry) tokens.Scope {
scope = tokens.Scope{
ProjectID: role.ProjectID,
}
case role.ProjectName != "" && (role.ProjectDomainName != "" || role.ProjectDomainID != ""):
scope = tokens.Scope{
ProjectName: role.ProjectName,
DomainName: role.ProjectDomainName,
DomainID: role.ProjectDomainID,
}
case role.ProjectName != "":
scope = tokens.Scope{
ProjectName: role.ProjectName,
Expand Down
71 changes: 48 additions & 23 deletions openstack/path_static_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,23 @@ func (b *backend) staticRoleExistenceCheck(ctx context.Context, r *logical.Reque
}

type roleStaticEntry struct {
Name string `json:"name"`
Cloud string `json:"cloud"`
TTL time.Duration `json:"ttl,omitempty"`
RotationDuration time.Duration `json:"rotation_duration,omitempty"`
SecretType secretType `json:"secret_type"`
Secret string `json:"secret"`
Username string `json:"username"`
UserID string `json:"user_id"`
ProjectID string `json:"project_id"`
ProjectName string `json:"project_name"`
DomainID string `json:"domain_id"`
DomainName string `json:"domain_name"`
Extensions map[string]string `json:"extensions"`
Name string `json:"name"`
Cloud string `json:"cloud"`
TTL time.Duration `json:"ttl,omitempty"`
RotationDuration time.Duration `json:"rotation_duration,omitempty"`
SecretType secretType `json:"secret_type"`
Secret string `json:"secret"`
Username string `json:"username"`
UserID string `json:"user_id"`
ProjectID string `json:"project_id"`
ProjectName string `json:"project_name"`
DomainID string `json:"domain_id"`
DomainName string `json:"domain_name"`
UserDomainID string `json:"user_domain_id"`
UserDomainName string `json:"user_domain_name"`
ProjectDomainID string `json:"project_domain_id"`
ProjectDomainName string `json:"project_domain_name"`
Extensions map[string]string `json:"extensions"`
}

func roleStaticStoragePath(name string) string {
Expand Down Expand Up @@ -193,15 +197,19 @@ func getStaticRoleByName(ctx context.Context, name string, s *logical.Request) (

func staticRoleToMap(src *roleStaticEntry) map[string]interface{} {
return map[string]interface{}{
"cloud": src.Cloud,
"rotation_duration": src.RotationDuration,
"secret_type": string(src.SecretType),
"username": src.Username,
"project_id": src.ProjectID,
"project_name": src.ProjectName,
"domain_id": src.DomainID,
"domain_name": src.DomainName,
"extensions": src.Extensions,
"cloud": src.Cloud,
"rotation_duration": src.RotationDuration,
"secret_type": string(src.SecretType),
"username": src.Username,
"project_id": src.ProjectID,
"project_name": src.ProjectName,
"domain_id": src.DomainID,
"domain_name": src.DomainName,
"user_domain_id": src.UserDomainID,
"user_domain_name": src.UserDomainName,
"project_domain_id": src.ProjectDomainID,
"project_domain_name": src.ProjectDomainName,
"extensions": src.Extensions,
}
}

Expand Down Expand Up @@ -255,16 +263,25 @@ func (b *backend) pathStaticRoleUpdate(ctx context.Context, req *logical.Request
entry = &roleStaticEntry{Name: name, Cloud: cloudName}
}

if name, ok := d.GetOk("user_domain_name"); ok {
entry.UserDomainName = name.(string)
}

if id, ok := d.GetOk("user_domain_id"); ok {
entry.UserDomainID = id.(string)
}

if username, ok := d.GetOk("username"); ok {
entry.Username = username.(string)
password, err := Passwords{}.Generate(ctx)
if err != nil {
return nil, err
}

// TODO: implement situation where userDomainId != currentDomainID
userId, err := b.rotateUserPassword(ctx, req, cloud, username.(string), password)
if err != nil {
return logical.ErrorResponse("error during role creation: %s", err), nil
return logical.ErrorResponse("error during role creation: %w", err), nil
}

entry.UserID = userId
Expand Down Expand Up @@ -304,6 +321,14 @@ func (b *backend) pathStaticRoleUpdate(ctx context.Context, req *logical.Request
entry.DomainID = id.(string)
}

if name, ok := d.GetOk("project_domain_name"); ok {
entry.ProjectDomainName = name.(string)
}

if id, ok := d.GetOk("project_domain_id"); ok {
entry.ProjectDomainID = id.(string)
}

if ext, ok := d.GetOk("extensions"); ok {
entry.Extensions = ext.(map[string]string)
}
Expand Down
22 changes: 13 additions & 9 deletions openstack/path_static_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,19 @@ func expectedStaticRoleData(cloudName string) (*roleStaticEntry, map[string]inte
DomainName: tools.RandomString("d", 5),
}
expectedMap := map[string]interface{}{
"cloud": expected.Cloud,
"project_id": "",
"project_name": expected.ProjectName,
"domain_id": "",
"domain_name": expected.DomainName,
"extensions": map[string]string{},
"rotation_duration": expTTL,
"secret_type": "token",
"username": "static-test",
"cloud": expected.Cloud,
"project_id": "",
"project_name": expected.ProjectName,
"domain_id": "",
"domain_name": expected.DomainName,
"project_domain_id": "",
"project_domain_name": "",
"user_domain_id": "",
"user_domain_name": "",
"extensions": map[string]string{},
"rotation_duration": expTTL,
"secret_type": "token",
"username": "static-test",
}
return expected, expectedMap
}
Expand Down

0 comments on commit 8f63890

Please sign in to comment.