Skip to content

Commit f206120

Browse files
authored
AV-43704: initial new cred and revoke of db creds implementation using v4 cp open api (#1)
* AV-43704: initial new cred and revoke of db creds implementation using v4 cp open api * refactored to accept the access in the creation statement of role config * Refactored the config and adding rotate-root support (wip) * fixed the issue with static role creation and also updated to newer lib * fixed the list of creds issue * updating the sample commands * fixed the pagination handling during the finding of db cred key * changed the module * partial fix for the rotate key saving of vault configuration * updated the example for capella dev env * Fixed the side effect of change AV-59622 * Fixed the side effect of change AV-59622 * Removed allowed roles * added back mandatory fields in the database config * trying to optimize the saving new password * Adding default and more examples to cover the default and all buckets scenarios * Fixing the root rotate issue and considering the password policy * Changed root rotation per new v4 api changes * Changed root rotation per new v4 api changes * Updating the latest steps * Few more updates * Reformatting to have easy copy of code * Reformatting to have easy copy of code * Reformatting to have easy copy of code * Update README.md
1 parent 80c7a94 commit f206120

21 files changed

Lines changed: 2953 additions & 250 deletions

README.md

Lines changed: 149 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The plugin supports the generation of static and dynamic user roles and root cre
1010

1111
## Build
1212

13-
To build this package for any platform you will need to clone this repository and cd into the repo directory and `go build -o couchbasecapella-database-plugin ./cmd/couchbasecapella-database-plugin/`. To test `go test` will execute a set of basic tests against against the docker.io/couchbase/server-sandbox:6.5.0 couchbase database image. To test against different sandbox images, for example 5.5.1, set the `COUCHBASE_VERSION=5.5.1` environment variable. If you want to run the tests against a local couchbase installation or an already running couchbase container, set the environment variable `COUCHBASE_HOST` before executing. **Note** you will need to align the Administrator username, password and bucket_name with the pre-set values in the `couchbasecapella_test.go` file. Set VAULT_ACC to execute all of the tests. A subset of tests can be run using the command `go test -run TestDriver/Init` for example.
13+
To build this package for any platform you will need to clone this repository and cd into the repo directory and `go build -o couchbasecapella-database-plugin ./cmd/couchbasecapella-database-plugin/`.
1414

1515
## Installation
1616

@@ -24,121 +24,203 @@ You will need to define a plugin directory using the `plugin_directory` configur
2424
Sample commands for registering and starting to use the plugin:
2525

2626
```bash
27-
$ SHA256=$(shasum -a 256 plugins/couchbasecapella-database-plugin | cut -d' ' -f1)
27+
SHA256=$(shasum -a 256 plugins/couchbasecapella-database-plugin | cut -d' ' -f1)
2828

29-
$ vault secrets enable database
29+
vault secrets enable database
3030

31-
$ vault write sys/plugins/catalog/database/couchbasecapella-database-plugin sha256=$SHA256 \
31+
vault write sys/plugins/catalog/database/couchbasecapella-database-plugin sha256=$SHA256 \
3232
command=couchbasecapella-database-plugin
3333
```
3434

3535
At this stage you are now ready to initialize the plugin to connect to couchbase capella cluster using unencrypted or encrypted communications.
3636

37-
Prior to initializing the plugin, ensure that you have created an administration account. Vault will use the user specified here to create/update/revoke database credentials. That user must have the appropriate permissions to perform actions upon other database users.
37+
Prior to initializing the plugin, ensure that you have created a couchbase capella provisioned cluster along with V4 API keys. Vault will use the user specified settings here to create/update/revoke database credentials. That user must have the appropriate permissions to perform actions upon other database users.
3838

39-
### Unencrypted plugin initialization
39+
### Plugin initialization
40+
41+
#### Set Vault Address to the local or hosted server
4042

4143
```bash
42-
$ vault write database/config/insecure-couchbasecapella plugin_name="couchbasecapella-database-plugin" \
43-
hosts="localhost" username="Administrator" password="password" \
44-
bucket_name="travel-sample" \ # only needed for pre-6.5.0 clusters
45-
allowed_roles="insecure-couchbasecapella-admin-role,insecure-couchbasecapella-*-bucket-role,static-account"
44+
export VAULT_ADDR=http://127.0.0.1:8200
45+
```
4646

47-
# You should consider rotating the admin password. Note that if you do, the new password will never be made available
48-
# through Vault, so you should create a vault-specific database admin user for this.
49-
$ vault write -force database/rotate-root/insecure-couchbasecapella
47+
#### Set the Capella required password policy
5048

51-
```
49+
```bash
5250

53-
Note: If you want to connect the plugin to a couchbase capella cluster prior to version 6.5.0 you will also have to supply an existing bucket (bucket_name="travel-sample") or the command will fail with the error message **"error verifying connection: error in Connection waiting for cluster: unambiguous timeout"**.
51+
cat >password_policy.hcl << EOF
52+
length=64
53+
54+
rule "charset" {
55+
charset = "abcdefghijklmnopqrstuvwxyz"
56+
min-chars = 1
57+
}
58+
59+
rule "charset" {
60+
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
61+
min-chars = 1
62+
}
63+
64+
rule "charset" {
65+
charset = "0123456789"
66+
min-chars = 1
67+
}
68+
69+
rule "charset" {
70+
charset = "#@%!"
71+
min-chars = 1
72+
}
73+
EOF
74+
75+
vault write sys/policies/password/couchbasecapella \
76+
policy=@password_policy.hcl
77+
```
78+
79+
<code>Success! Data written to: sys/policies/password/couchbasecapella</code>
80+
81+
#### Initialize the database plugin
82+
83+
<code># Usage
84+
vault write database/config/couchbasecapella-database \
85+
plugin_name="couchbasecapella-database-plugin" \
86+
cloud_api_base_url="https://cloudapi.dev.nonprod-project-avengers.com/v4" \
87+
organization_id="<org_uuid>" \
88+
project_id="<proj_uuid>" \
89+
cluster_id="<cluster_uuid>" \
90+
username="<v4-access-api-key>" \
91+
password='<v4-secret-api-key>' \
92+
password_policy="couchbasecapella" \
93+
allowed_roles="*"
94+
</code>
5495

55-
### Encrypted plugin initialization
96+
```bash
97+
vault write database/config/couchbasecapella-database \
98+
plugin_name="couchbasecapella-database-plugin" \
99+
cloud_api_base_url="https://cloudapi.dev.nonprod-project-avengers.com/v4" \
100+
organization_id="6af08c0a-8cab-4c1c-b257-b521575c16d0" \
101+
project_id="d352361d-8de1-445b-9969-873b6decb63a" \
102+
cluster_id="47820643-6e1f-4fea-b63c-625d1e10b536" \
103+
username="haI3UAw1VEOGvxjBFWDEBhnpB0nF74qf" \
104+
password='EUlfonfGtx#s1RlqPYryIlmcBgWuTIaMgvJ6%lrQIdu5QwCDW5oJRdeVwa3qynh7' \
105+
password_policy="couchbasecapella" \
106+
allowed_roles="*"
107+
```
108+
<code>Success! Data written to: database/config/couchbasecapella-database</code>
56109

57-
The example here uses the self signed CA certificate that comes with the out of the box couchbase cluster installation and is not suitable for real production use where commercial grade certificates should be obtained.
110+
You should consider rotating the root password (same as secretKey). Note that if you do, the new password(secret) will never be made available through Vault, so you should create a vault-specific database admin user for this.
58111

59112
```bash
60-
$ BASE64PEM=$(curl -X GET http://Administrator:Admin123@127.0.0.1:8091/pools/default/certificate|base64 -w0)
113+
vault write -force database/rotate-root/couchbasecapella-database
114+
```
115+
<code>Success! Data written to: database/rotate-root/couchbasecapella-database</code>
116+
117+
### Dynamic Role Creation
61118

62-
$ vault write database/config/secure-couchbasecapella plugin_name="couchbasecapella-database-plugin" \
63-
hosts="couchbases://localhost" username="Administrator" password="password" \
64-
tls=true base64pem=${BASE64PEM} \
65-
bucket_name="travel-sample" \ # only needed for pre-6.5.0 clusters
66-
allowed_roles="secure-couchbasecapella-admin-role,secure-couchbasecapella-*-bucket-role,static-account"
119+
When you create roles, you need to provide a JSON string containing the access with Couchbase RBAC roles which are documented [here](http://cbc-cp-api.s3-website-us-east-1.amazonaws.com/#tag/databaseCredentials/operation/postDatabaseCredential).
67120

68-
# You should consider rotating the admin password. Note that if you do, the new password will never be made available
69-
# through Vault, so you should create a vault-specific database admin user for this.
70-
$ vault write -force database/rotate-root/secure-couchbasecapella
121+
NOTE: if a creation_statement is not provided readonly for all buckets(with all scopes and collections), <code>'{access: [{ privileges: [ data_reader ], resources: { buckets: [ { name :* } ] } }]}'</code>
122+
123+
#### dynamicrole1 with a specific bucket, scope with both data read and write.
124+
125+
```bash
126+
vault write database/roles/dynamicrole1 db_name="couchbasecapella-database" creation_statements='{"access": [ { "privileges": [ "data_reader", "data_writer" ], "resources": { "buckets": [ { "name": "vault-bucket-1", "scopes": [ { "name": "vault-bucket-1-scope-1", "collections": [ "*" ] } ] } ] } } ]}' default_ttl="5m" max_ttl="1h"
71127
```
72128

73-
### Dynamic Role Creation
129+
<code>Success! Data written to: database/roles/dynamicrole1</code>
74130

75-
When you create roles, you need to provide a JSON string containing the Couchbase RBAC roles which are documented [here](https://docs.couchbase.com/server/6.5/learn/security/roles.html). From Couchbase 6.5 groups are supported and the creation statement can contain just roles or just groups or a mixture of the two. **Note** to use a group, it must have been created in the database previously.
131+
#### dynamicrole2 with a list of 3 buckets (its all scopes &collections) access previleges of both data read and write.
76132

77133
```bash
78-
# if a creation_statement is not provided the user account will default to read only admin, '{"roles":[{"role":"ro_admin"}]}'
79-
$ vault write database/roles/insecure-couchbasecapella-admin-role db_name=insecure-couchbasecapella \
80-
default_ttl="5m" max_ttl="1h" creation_statements='{"roles":[{"role":"admin"}],"groups":["Supervisor"]}'
134+
vault write database/roles/dynamicrole2 db_name="couchbasecapella-database" creation_statements='{"access": [ { "privileges": [ "data_reader", "data_writer" ], "resources": { "buckets": [ { "name": "db-cred-test-12Qj", "scopes": [ { "name": "*" } ] }, { "name": "db-cred-test-3zRb", "scopes": [ { "name": "*" } ] }, { "name": "db-cred-test-FcAv", "scopes": [ { "name": "*" } ] } ] } } ]}' default_ttl="5m" max_ttl="1h"
135+
```
136+
137+
<code>Success! Data written to: database/roles/dynamicrole2</code>
81138

82-
$ vault write database/roles/insecure-couchbasecapella-travel-sample-bucket-role db_name=insecure-couchbasecapella \
83-
default_ttl="5m" max_ttl="1h" creation_statements='{"roles":[{"role":"bucket_full_access","bucket_name":"travel-sample"}]}'
84-
Success! Data written to: database/roles/insecure-couchbasecapella-travel-sample-bucket-role
139+
#### dynamicrole3 with all buckets
140+
```bash
141+
vault write database/roles/dynamicrole3 db_name="couchbasecapella-database" creation_statements='{"access": [ { "privileges": [ "data_reader" ], "resources": { "buckets": [ { "name": "*" } ] } } ]}' default_ttl="5m" max_ttl="1h"
85142
```
86143

87-
If you create a role that uses groups on a pre 6.5 couchbase server it will be successful, but when you try to generate credentials
88-
you will receive the error **rpc error: code = Unknown desc = {"errors":{"groups":"Unsupported key"}} ...**
144+
<code>Success! Data written to: database/roles/dynamicrole3</code>
145+
89146

90147
To retrieve the credentials for the dynamic accounts
91148

92149
```bash
150+
vault read database/creds/dynamicrole1
151+
```
152+
<code>Key Value
153+
--- -----
154+
lease_id database/creds/dynamicrole1/qyaXNzyh53U1w5zIlSHAR7be
155+
lease_duration 5m
156+
lease_renewable true
157+
password !maOKglPc7IIccb!CftAC7rLsXQlxUvGKgpEnzzAYpbiifcYfVpF3E8jiyNvABtN
158+
username V_TOKEN_MYDYNAMICROLE3_OAKVWMKBMT1P9IGJS1TT_1692391736
159+
</code>
93160

94-
$ vault read database/creds/insecure-couchbasecapella-admin-role
95-
Key Value
96-
--- -----
97-
lease_id database/creds/insecure-couchbasecapella-admin-role/KJ7CTmpFni6U6BCDJ14HcmDm
98-
lease_duration 5m
99-
lease_renewable true
100-
password A1a-yCSH5rAh8QAkCzwu
101-
username v-token-insecure-couchbasecapella-admin-role-yA2hgb0tfewf
102-
103-
$ vault read database/creds/insecure-couchbasecapella-travel-sample-bucket-role
104-
Key Value
105-
--- -----
106-
lease_id database/creds/insecure-couchbasecapella-travel-sample-bucket-role/OzHdfkIZdeY9p8kjdWur512j
107-
lease_duration 5m
108-
lease_renewable true
109-
password A1a-0yTIuO4q0dCvphz1
110-
username v-token-insecure-couchbasecapella-travel-sample-bucket-role-iN5
111161

162+
```bash
163+
vault read database/creds/dynamicrole2
164+
```
165+
166+
<code>Key Value
167+
--- -----
168+
lease_id database/creds/dynamicrole2/spZ3NGneJYkptKnTgru8MJlb
169+
lease_duration 5m
170+
lease_renewable true
171+
password AVPuT#oF0cGzPIfBnaqMGsHjm9vorXwaOUw8ezP7b1k@mbTwBcRL72c@7@NDDyr0
172+
username V_TOKEN_MYDYNAMICROLE3_8O0M5CKNWNSF2EA6VYLW_1692391739
173+
</code>
174+
175+
```bash
176+
vault read database/creds/dynamicrole3
112177
```
113178

179+
<code>Key Value
180+
--- -----
181+
lease_id database/creds/dynamicrole3/5ok4TllgYHg6QH4vsawqt2mY
182+
lease_duration 5m
183+
lease_renewable true
184+
password VVE2T4AkW%LsUgBGsY5c8opt09gB2vWhefUyJ@%qPxy%saO56d4ZPntiHkQDmiUf
185+
username V_TOKEN_MYDYNAMICROLE3_ZOFAJPGLNZNQMSZCBUFK_1692391706
186+
</code>
187+
114188
### Static Role Creation
115189

116-
In order to use static roles, the user must already exist in the Couchbase Capella security settings. The example below assumes that there is an existing user with the name "vault-edu". If the user does not exist you will receive the following error.
190+
In order to use static roles, the database credential user must already exist in the Couchbase Capella security settings. The example below assumes that there is an existing user with the name "vault-edu".
191+
117192

118193
```bash
119-
* 1 error occurred:
120-
* error setting credentials: rpc error: code = Unknown desc = user not found | {"unique_id":"74f229fd-b3b3-4036-9673-312adae094bb","endpoint":"http://localhost:8091"}
194+
195+
# Usage:
196+
vault write database/static-roles/<role-name> db_name=couchbasecapella-database username="<db-cred-user-name>" rotation_period=<secs>
121197
```
122198

199+
<code>Success! Data written to: database/static-roles/<role-name></code>
200+
123201
```bash
124-
$ vault write database/static-roles/static-account db_name=insecure-couchbasecapella \
202+
# Example:
203+
vault write database/static-roles/static-account db_name=couchbasecapella-database \
125204
username="vault-edu" rotation_period="5m"
126-
Success! Data written to: database/static-roles/static-account
127-
````
205+
```
206+
207+
<code>Success! Data written to: database/static-roles/static-account</code>
128208

129209
To retrieve the credentials for the vault-edu user
130210

131211
```bash
132-
$ vault read database/static-creds/static-account
133-
Key Value
134-
--- -----
135-
last_vault_rotation 2020-06-15T14:32:16.682130141-05:00
136-
password A1a-09ApRvglZY1Usdjp
137-
rotation_period 5m
138-
ttl 30s
139-
username vault-edu
212+
vault read database/static-creds/static-account
140213
```
141214

215+
<code>Key Value
216+
--- -----
217+
last_vault_rotation 2023-08-04T19:28:21.229382-07:00
218+
password wFgNaxdH2wGw2i9-B0Qj
219+
rotation_period 5m
220+
ttl 4m59s
221+
username vault-edu
222+
</code>
223+
142224
## Developing
143225

144226
You can run `make dev` in the root of the repo to start up a development vault server and automatically register a local build of the plugin. You will need to have a built `vault` binary available in your `$PATH` to do so.

cmd/couchbasecapella-database-plugin/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"os"
55

66
hclog "github.com/hashicorp/go-hclog"
7-
couchbase "github.com/hashicorp/vault-plugin-database-couchbase"
7+
couchbasecapella "github.com/hashicorp/vault-plugin-database-couchbasecapella"
88
dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
99
)
1010

@@ -20,7 +20,7 @@ func main() {
2020

2121
// Run instantiates a CouchbaseDB object, and runs the RPC server for the plugin
2222
func Run() error {
23-
dbplugin.ServeMultiplex(couchbase.New)
23+
dbplugin.ServeMultiplex(couchbasecapella.New)
2424

2525
return nil
2626
}

connection_producer.go

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,26 @@ import (
1010

1111
"github.com/couchbase/gocb/v2"
1212
"github.com/hashicorp/errwrap"
13+
"github.com/hashicorp/go-hclog"
1314
"github.com/hashicorp/vault/sdk/database/helper/connutil"
1415
"github.com/mitchellh/mapstructure"
1516
)
1617

1718
type couchbaseCapellaDBConnectionProducer struct {
18-
AccessKey string `json:"access_key"`
19-
SecretKey string `json:"secret_key"`
19+
Username string `json:"username"`
20+
Password string `json:"password"`
21+
OrganizationID string `json:"organization_id"`
22+
ProjectID string `json:"project_id"`
2023
ClusterID string `json:"cluster_id"`
2124
ClusterType string `json:"cluster_type"`
2225
CloudAPIBaseURL string `json:"cloud_api_base_url"`
26+
ConnectURL string `json:"connect_url"`
2327
CloudAPIClustersPath string `json:"cloud_api_clusters_path"`
2428
BucketName string `json:"bucket_name"`
25-
ScopeName string `json:"scope_name"`
2629
AccessRole string `json:"access_role"`
2730

31+
logger hclog.Logger
2832
Hosts string `json:"hosts"`
29-
Username string `json:"username"`
30-
Password string `json:"password"`
3133
TLS bool `json:"tls"`
3234
InsecureTLS bool `json:"insecure_tls"`
3335
Base64Pem string `json:"base64pem"`
@@ -41,7 +43,7 @@ type couchbaseCapellaDBConnectionProducer struct {
4143

4244
func (c *couchbaseCapellaDBConnectionProducer) secretValues() map[string]string {
4345
return map[string]string{
44-
c.Password: "[password]",
46+
c.Password: "{{password}}",
4547
c.Username: "[username]",
4648
}
4749
}
@@ -51,8 +53,11 @@ func (c *couchbaseCapellaDBConnectionProducer) Init(ctx context.Context, initCon
5153
c.Lock()
5254
defer c.Unlock()
5355

54-
c.rawConfig = initConfig
55-
56+
c.logger = hclog.New(&hclog.LoggerOptions{})
57+
if c.rawConfig == nil {
58+
c.logger.Info("Init, setting initconfig")
59+
c.rawConfig = initConfig
60+
}
5661
decoderConfig := &mapstructure.DecoderConfig{
5762
Result: c,
5863
WeaklyTypedInput: true,
@@ -70,12 +75,16 @@ func (c *couchbaseCapellaDBConnectionProducer) Init(ctx context.Context, initCon
7075
}
7176

7277
switch {
78+
case len(c.OrganizationID) == 0:
79+
return nil, fmt.Errorf("organization_id cannot be empty")
80+
case len(c.ProjectID) == 0:
81+
return nil, fmt.Errorf("project_id cannot be empty")
7382
case len(c.ClusterID) == 0:
7483
return nil, fmt.Errorf("cluster_id cannot be empty")
75-
case len(c.AccessKey) == 0:
76-
return nil, fmt.Errorf("access_key cannot be empty")
77-
case len(c.SecretKey) == 0:
78-
return nil, fmt.Errorf("secret_key cannot be empty")
84+
case len(c.Username) == 0:
85+
return nil, fmt.Errorf("root username (access_key) cannot be empty")
86+
case len(c.Password) == 0:
87+
return nil, fmt.Errorf("rootuser password (secret_key) cannot be empty")
7988
}
8089

8190
if len(c.CloudAPIBaseURL) == 0 {
@@ -89,12 +98,11 @@ func (c *couchbaseCapellaDBConnectionProducer) Init(ctx context.Context, initCon
8998
} else if len(c.CloudAPIClustersPath) == 0 && c.ClusterType == "invpc" {
9099
c.CloudAPIClustersPath = "/v2/clusters"
91100
}
101+
c.CloudAPIClustersPath = fmt.Sprintf("/organizations/%s/projects/%s/clusters/%s", c.OrganizationID, c.ProjectID, c.ClusterID)
102+
92103
if len(c.AccessRole) == 0 {
93104
c.AccessRole = "data_writer"
94105
}
95-
if len(c.ScopeName) == 0 {
96-
c.ScopeName = "*"
97-
}
98106

99107
if c.TLS {
100108
if len(c.Base64Pem) == 0 {
@@ -116,6 +124,11 @@ func (c *couchbaseCapellaDBConnectionProducer) Init(ctx context.Context, initCon
116124
}
117125
}
118126

127+
if c.secretValues()["Password"] != "" {
128+
c.logger.Info("couchbaseCapellaDBConnectionProducer, init, setting the password to the secret values ")
129+
initConfig["password"] = c.secretValues()["Password"]
130+
}
131+
119132
return initConfig, nil
120133
}
121134

0 commit comments

Comments
 (0)