diff --git a/acceptance/creds_test.go b/acceptance/creds_test.go index dc8419c..594a6b3 100644 --- a/acceptance/creds_test.go +++ b/acceptance/creds_test.go @@ -6,7 +6,6 @@ package acceptance import ( "fmt" "net/http" - "os" "testing" "github.com/opentelekomcloud/vault-plugin-secrets-openstack/openstack" @@ -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) @@ -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"}, diff --git a/acceptance/static_creds_test.go b/acceptance/static_creds_test.go index ac507e5..4211d14 100644 --- a/acceptance/static_creds_test.go +++ b/acceptance/static_creds_test.go @@ -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() { @@ -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 { @@ -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, }) } diff --git a/doc/source/api.md b/doc/source/api.md index 3125378..fcbb9b6 100644 --- a/doc/source/api.md +++ b/doc/source/api.md @@ -182,10 +182,10 @@ created. If the role exists, it will be updated with the new attributes. `domain_id`. - `user_domain_id` `(string: )` - 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: )` - 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: )` - 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. @@ -472,7 +472,19 @@ created. If the role exists, it will be updated with the new attributes. - `domain_name` `(string: )` - 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: )` - Specifies domain id of existing user. + Mutually exclusive with `user_domain_name`. + +- `user_domain_name` `(string: )` - Specifies domain name of existing user. + Mutually exclusive with `user_domain_id`. + +- `project_domain_id` `(string: )` - 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: )` - 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 diff --git a/openstack/path_cloud.go b/openstack/path_cloud.go index b631bfe..22867b2 100644 --- a/openstack/path_cloud.go +++ b/openstack/path_cloud.go @@ -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 diff --git a/openstack/path_static_creds.go b/openstack/path_static_creds.go index d4b8b9a..c5caf52 100644 --- a/openstack/path_static_creds.go +++ b/openstack/path_static_creds.go @@ -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, diff --git a/openstack/path_static_role.go b/openstack/path_static_role.go index 218dc03..7917123 100644 --- a/openstack/path_static_role.go +++ b/openstack/path_static_role.go @@ -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 { @@ -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, } } @@ -255,6 +263,14 @@ 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) @@ -262,9 +278,10 @@ func (b *backend) pathStaticRoleUpdate(ctx context.Context, req *logical.Request 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 @@ -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) } diff --git a/openstack/path_static_role_test.go b/openstack/path_static_role_test.go index eaf6fdd..25e8672 100644 --- a/openstack/path_static_role_test.go +++ b/openstack/path_static_role_test.go @@ -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 }