Skip to content

Commit

Permalink
Clouds: add rotation parameters for auto rotate (#127)
Browse files Browse the repository at this point in the history
Path_Clouds: add rotation parameters for future auto rotate root implementation

Path clouds is updated with additional parameters for auto rotate root with periodicfunc.
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 ixqy was created
plugin_test.go:337: Cloud with name ixqy 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.04s)
--- PASS: TestPlugin/TestCloudLifecycle (0.05s)
--- PASS: TestPlugin/TestCloudLifecycle/WriteCloud (0.05s)
--- 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 (8.18s)
--- PASS: TestPlugin/TestCredsLifecycle/user_domain_id_token (3.05s)
--- PASS: TestPlugin/TestCredsLifecycle/root_token (0.83s)
--- PASS: TestPlugin/TestCredsLifecycle/user_token (2.44s)
--- PASS: TestPlugin/TestCredsLifecycle/user_password (1.03s)
--- FAIL: TestPlugin/TestInfo (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle (0.61s)
--- PASS: TestPlugin/TestRoleLifecycle/WriteRole (0.60s)
--- PASS: TestPlugin/TestRoleLifecycle/ReadRole (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/ListRoles (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/ListRoles/method-LIST (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/ListRoles/method-GET (0.00s)
--- PASS: TestPlugin/TestRoleLifecycle/DeleteRole (0.00s)
--- PASS: TestPlugin/TestRootRotate (4.69s)
--- PASS: TestPlugin/TestStaticCredsLifecycle (15.61s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_password (3.26s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_token_project_id (3.83s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_token_project_name (3.76s)
--- PASS: TestPlugin/TestStaticCredsLifecycle/user_domain_id_token (3.70s)
--- PASS: TestPlugin/TestStaticRoleLifecycle (2.78s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/WriteRole (1.03s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ReadRole (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ListRoles (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ListRoles/method-GET (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/ListRoles/method-LIST (0.00s)
--- PASS: TestPlugin/TestStaticRoleLifecycle/DeleteRole (0.00s)
FAIL
FAIL    github.com/opentelekomcloud/vault-plugin-secrets-openstack/acceptance   32.382s
FAIL
make: *** [functional] Error 1

Reviewed-by: Aloento
  • Loading branch information
artem-lifshits authored Feb 8, 2023
1 parent 8f63890 commit c8ee9df
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 18 deletions.
5 changes: 4 additions & 1 deletion doc/source/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ will overwrite them.

* `password` `(string: <required>)` - OpenStack password of the root user.

* `root_password_ttl` `(string: <optional>)` - Password rotation period. Default period is six month.

* `username_template` `(string: "vault{{random 8 | lowercase}}")` - Template used for usernames
of temporary users. For details on templating syntax please refer to
[Username Templating](https://www.vaultproject.io/docs/concepts/username-templating). Additional
Expand All @@ -39,7 +41,8 @@ will overwrite them.
"username": "admin",
"password": "RcigTiYrJjVmEkrV71Cd",
"user_domain_name": "Default",
"username_template": "user-{{ .RoleName }}-{{ random 4 }}"
"username_template": "user-{{ .RoleName }}-{{ random 4 }}",
"root_password_ttl": "5h"
}
```

Expand Down
10 changes: 0 additions & 10 deletions openstack/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,3 @@ func (c *sharedCloud) initClient(ctx context.Context, s logical.Storage) error {

return nil
}

type OsCloud struct {
Name string `json:"name"`
AuthURL string `json:"auth_url"`
UserDomainName string `json:"user_domain_name"`
Username string `json:"username"`
Password string `json:"password"`
UsernameTemplate string `json:"username_template"`
PasswordPolicy string `json:"password_policy"`
}
32 changes: 31 additions & 1 deletion openstack/path_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
"github.com/opentelekomcloud/vault-plugin-secrets-openstack/vars"
"time"
)

const (
Expand All @@ -20,6 +21,7 @@ Configure the root credentials for an OpenStack cloud using the above parameters
pathCloudListHelpDesc = `List existing OpenStack clouds by name.`

DefaultUsernameTemplate = "vault{{random 8 | lowercase}}"
defaultRootPasswordTTL = 4380 * time.Hour
)

func storageCloudKey(name string) string {
Expand All @@ -30,6 +32,18 @@ func pathCloudKey(name string) string {
return fmt.Sprintf("%s/%s", pathCloud, name)
}

type OsCloud struct {
Name string `json:"name"`
AuthURL string `json:"auth_url"`
UserDomainName string `json:"user_domain_name"`
Username string `json:"username"`
Password string `json:"password"`
UsernameTemplate string `json:"username_template"`
PasswordPolicy string `json:"password_policy"`
RootPasswordTTL time.Duration `json:"root_password_ttl"`
RootPasswordExpirationDate time.Time `json:"root_password_expiration_date"`
}

func (c *sharedCloud) getCloudConfig(ctx context.Context, s logical.Storage) (*OsCloud, error) {
entry, err := s.Get(ctx, storageCloudKey(c.name))
if err != nil {
Expand Down Expand Up @@ -95,6 +109,12 @@ func (b *backend) pathCloud() *framework.Path {
Type: framework.TypeString,
Description: "Name of the password policy to use to generate passwords for dynamic credentials.",
},
"root_password_ttl": {
Type: framework.TypeDurationSecond,
Default: defaultRootPasswordTTL,
Description: "The TTL of the root password for openstack user. This can be either a number of seconds or a time formatted duration (ex: 24h, 48ds)",
Required: false,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.CreateOperation: &framework.PathOperation{
Expand Down Expand Up @@ -176,13 +196,21 @@ func (b *backend) pathCloudCreateUpdate(ctx context.Context, r *logical.Request,
if err != nil {
return logical.ErrorResponse("invalid username template: %w", err), nil
}
} else if r.Operation == logical.CreateOperation {
} else if r.Operation == logical.CreateOperation && cloudConfig.UsernameTemplate == "" {
cloudConfig.UsernameTemplate = DefaultUsernameTemplate
}
if pwdPolicy, ok := d.GetOk("password_policy"); ok {
cloudConfig.PasswordPolicy = pwdPolicy.(string)
}

if rootExpirationRaw, ok := d.GetOk("root_password_ttl"); ok {
cloudConfig.RootPasswordTTL = time.Second * time.Duration(rootExpirationRaw.(int))
} else if r.Operation == logical.CreateOperation && cloudConfig.RootPasswordTTL == 0 {
cloudConfig.RootPasswordTTL = defaultRootPasswordTTL
}

cloudConfig.RootPasswordExpirationDate = time.Now().Add(cloudConfig.RootPasswordTTL)

sCloud.passwords = &Passwords{
PolicyGenerator: b.System(),
PolicyName: cloudConfig.PasswordPolicy,
Expand Down Expand Up @@ -212,6 +240,8 @@ func (b *backend) pathCloudRead(ctx context.Context, r *logical.Request, d *fram
"username": cloudConfig.Username,
"username_template": cloudConfig.UsernameTemplate,
"password_policy": cloudConfig.PasswordPolicy,
"root_password_ttl": int(cloudConfig.RootPasswordTTL.Seconds()),
"next_rotation": cloudConfig.RootPasswordExpirationDate.Format(time.RFC822),
},
}, nil
}
Expand Down
98 changes: 92 additions & 6 deletions openstack/path_cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package openstack

import (
"context"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"strings"
"testing"
)

var (
Expand All @@ -25,7 +23,7 @@ var (
testPolicy2 = "openstack"
)

func TestCloudCreate(t *testing.T) {
func TestLifecyle(t *testing.T) {
t.Run("EmptyConfig", func(t *testing.T) {
b, storage := testBackend(t)

Expand Down Expand Up @@ -199,3 +197,91 @@ func TestCloudCreate(t *testing.T) {
assert.Len(t, res.Data["keys"], cloudCount)
})
}

func TestConfig(t *testing.T) {
b, s := testBackend(t)

tests := []struct {
name string
config map[string]interface{}
expected map[string]interface{}
wantErr bool
}{
{
name: "root_password_ttl defaults to 6 months",
config: map[string]interface{}{
"auth_url": "https://test-001.com/v3",
"username": "test-username-1",
"user_domain_name": "testUserDomainName",
"password": "testUserPassword",
"username_template": "user-{{ .RoleName }}-{{ random 4 }}",
},
expected: map[string]interface{}{
"auth_url": "https://test-001.com/v3",
"username": "test-username-1",
"user_domain_name": "testUserDomainName",
"username_template": "user-{{ .RoleName }}-{{ random 4 }}",
"root_password_ttl": 15768000,
"password_policy": "",
},
},
{
name: "root_password_ttl is provided",
config: map[string]interface{}{
"auth_url": "https://test-001.com/v3",
"username": "test-username-2",
"user_domain_name": "testUserDomainName",
"password": "testUserPassword",
"root_password_ttl": "1m",
},
expected: map[string]interface{}{
"auth_url": "https://test-001.com/v3",
"username": "test-username-2",
"user_domain_name": "testUserDomainName",
"password_policy": "",
"root_password_ttl": 60,
"username_template": "vault{{random 8 | lowercase}}"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var cloudName = strings.ToLower(tools.RandomString("cloud", 3))
testConfigCreateUpdate(t, b, s, tc.config, cloudName)
testConfigRead(t, b, s, tc.expected, cloudName)

// Test that updating one element retains the others
tc.expected["user_domain_name"] = "800e371d-ee51-4145-9ac8-5c43e4ceb79b"
configSubset := map[string]interface{}{
"user_domain_name": "800e371d-ee51-4145-9ac8-5c43e4ceb79b",
}

testConfigCreateUpdate(t, b, s, configSubset, cloudName)
testConfigRead(t, b, s, tc.expected, cloudName)
})
}
}

func testConfigCreateUpdate(t *testing.T, b logical.Backend, s logical.Storage, expected map[string]interface{}, name string) {
t.Helper()
_, err := b.HandleRequest(context.Background(), &logical.Request{
Storage: s,
Operation: logical.CreateOperation,
Path: pathCloudKey(name),
Data: expected,
})
require.NoError(t, err)
}

func testConfigRead(t *testing.T, b logical.Backend, s logical.Storage, expected map[string]interface{}, name string) {
t.Helper()
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Storage: s,
Operation: logical.ReadOperation,
Path: pathCloudKey(name),
})
require.NoError(t, err)

expected["next_rotation"] = resp.Data["next_rotation"]
assert.Equal(t, expected, resp.Data)
}

0 comments on commit c8ee9df

Please sign in to comment.