Skip to content

Commit 7d802bc

Browse files
Vivek PandeyVivek Pandey
authored andcommitted
add rotate-on-read option on ldap secrets
1 parent b8e6651 commit 7d802bc

7 files changed

Lines changed: 200 additions & 0 deletions

internal/consts/consts.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,8 @@ const (
864864
FieldAzureClientID = "azure_client_id"
865865
FieldAzureAuthMode = "azure_auth_mode"
866866
FieldSelfManaged = "self_managed"
867+
FieldRotateOnRead = "rotate_on_read"
868+
FieldRotateOnReadCooldown = "rotate_on_read_cooldown"
867869
FieldRotationPolicy = "rotation_policy"
868870
FieldAWSSecretAccessKeyWO = "aws_secret_access_key_wo"
869871
FieldSecretsWOVersion = "secrets_wo_version"

vault/resource_ldap_secret_backend.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ func ldapSecretBackendResource() *schema.Resource {
150150
ConflictsWith: []string{consts.FieldBindPass, consts.FieldBindPassWO},
151151
ExactlyOneOf: []string{consts.FieldBindPass, consts.FieldBindPassWO, consts.FieldSelfManaged},
152152
},
153+
consts.FieldRotateOnRead: {
154+
Type: schema.TypeBool,
155+
Optional: true,
156+
Computed: true,
157+
Description: "If true, static role credentials are rotated on each read. Acts as a default for all static roles. Requires Vault Enterprise ≥ 2.1.0.",
158+
},
159+
consts.FieldRotateOnReadCooldown: {
160+
Type: schema.TypeInt,
161+
Optional: true,
162+
Computed: true,
163+
Description: "Minimum seconds between rotate-on-read rotations. Acts as a default cooldown for all static roles. Requires Vault Enterprise ≥ 2.1.0.",
164+
},
153165
}
154166
resource := provider.MustAddMountMigrationSchema(&schema.Resource{
155167
CreateContext: provider.MountCreateContextWrapper(createUpdateLDAPConfigResource, provider.VaultVersion112),
@@ -258,6 +270,16 @@ func createUpdateLDAPConfigResource(ctx context.Context, d *schema.ResourceData,
258270
automatedrotationutil.ParseAutomatedRotationFields(d, data)
259271
}
260272

273+
// get rotate-on-read fields
274+
if provider.IsAPISupported(meta, provider.VaultVersion210) && provider.IsEnterpriseSupported(meta) {
275+
if v, ok := d.GetOk(consts.FieldRotateOnRead); ok {
276+
data[consts.FieldRotateOnRead] = v
277+
}
278+
if v, ok := d.GetOk(consts.FieldRotateOnReadCooldown); ok {
279+
data[consts.FieldRotateOnReadCooldown] = v
280+
}
281+
}
282+
261283
// get self-managed boolean
262284
if provider.IsAPISupported(meta, provider.VaultVersion200) && provider.IsEnterpriseSupported(meta) {
263285
if d.HasChange(consts.FieldSelfManaged) {
@@ -348,6 +370,19 @@ func readLDAPConfigResource(ctx context.Context, d *schema.ResourceData, meta in
348370
}
349371
}
350372

373+
if provider.IsAPISupported(meta, provider.VaultVersion210) && provider.IsEnterpriseSupported(meta) {
374+
if v, ok := resp.Data[consts.FieldRotateOnRead]; ok {
375+
if err := d.Set(consts.FieldRotateOnRead, v); err != nil {
376+
return diag.Errorf("error setting %s: %s", consts.FieldRotateOnRead, err)
377+
}
378+
}
379+
if v, ok := resp.Data[consts.FieldRotateOnReadCooldown]; ok {
380+
if err := d.Set(consts.FieldRotateOnReadCooldown, v); err != nil {
381+
return diag.Errorf("error setting %s: %s", consts.FieldRotateOnReadCooldown, err)
382+
}
383+
}
384+
}
385+
351386
return nil
352387
}
353388

vault/resource_ldap_secret_backend_static_role.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ func ldapSecretBackendStaticRoleResource() *schema.Resource {
6767
Optional: true,
6868
Description: "Name of the password policy to use to generate passwords for this role.",
6969
},
70+
consts.FieldRotateOnRead: {
71+
Type: schema.TypeBool,
72+
Optional: true,
73+
Description: "If true, credentials are rotated on each read. Overrides the engine-level default when set. Requires Vault Enterprise ≥ 2.1.0.",
74+
},
75+
consts.FieldRotateOnReadCooldown: {
76+
Type: schema.TypeInt,
77+
Optional: true,
78+
Description: "Minimum seconds between rotate-on-read rotations for this role. Overrides the engine-level default when set. Requires Vault Enterprise ≥ 2.1.0.",
79+
},
7080
}
7181
resource := &schema.Resource{
7282
CreateContext: createUpdateLDAPStaticRoleResource,
@@ -141,6 +151,16 @@ func createUpdateLDAPStaticRoleResource(ctx context.Context, d *schema.ResourceD
141151
}
142152
}
143153

154+
// get rotate-on-read role-level overrides
155+
if provider.IsAPISupported(meta, provider.VaultVersion210) && provider.IsEnterpriseSupported(meta) {
156+
if v, ok := d.GetOk(consts.FieldRotateOnRead); ok {
157+
data[consts.FieldRotateOnRead] = v
158+
}
159+
if v, ok := d.GetOk(consts.FieldRotateOnReadCooldown); ok {
160+
data[consts.FieldRotateOnReadCooldown] = v
161+
}
162+
}
163+
144164
if _, err := client.Logical().WriteWithContext(ctx, rolePath, data); err != nil {
145165
return diag.FromErr(fmt.Errorf("error writing %q: %s", rolePath, err))
146166
}
@@ -187,6 +207,20 @@ func readLDAPStaticRoleResource(ctx context.Context, d *schema.ResourceData, met
187207
}
188208
}
189209

210+
// read rotate-on-read role-level overrides (only present when explicitly set on the role)
211+
if provider.IsAPISupported(meta, provider.VaultVersion210) && provider.IsEnterpriseSupported(meta) {
212+
if v, ok := resp.Data[consts.FieldRotateOnRead]; ok {
213+
if err := d.Set(consts.FieldRotateOnRead, v); err != nil {
214+
return diag.Errorf("error setting %s: %s", consts.FieldRotateOnRead, err)
215+
}
216+
}
217+
if v, ok := resp.Data[consts.FieldRotateOnReadCooldown]; ok {
218+
if err := d.Set(consts.FieldRotateOnReadCooldown, v); err != nil {
219+
return diag.Errorf("error setting %s: %s", consts.FieldRotateOnReadCooldown, err)
220+
}
221+
}
222+
}
223+
190224
return nil
191225
}
192226

vault/resource_ldap_secret_backend_static_role_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,64 @@ resource "vault_ldap_secret_backend_static_role" "role" {
278278
}
279279
`, passwordPolicy, mount, bindDN, bindPass, url, username, dn, role, rotationPeriod)
280280
}
281+
282+
func TestAccLDAPSecretBackendStaticRole_RotateOnRead(t *testing.T) {
283+
path := acctest.RandomWithPrefix("tf-test-ldap-static-role")
284+
bindDN, bindPass, url := testutil.GetTestLDAPCreds(t)
285+
resourceType := "vault_ldap_secret_backend_static_role"
286+
resourceName := resourceType + ".role"
287+
username := "alice"
288+
dn := "cn=alice,ou=users,dc=example,dc=org"
289+
290+
resource.Test(t, resource.TestCase{
291+
ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(context.Background(), t),
292+
PreCheck: func() {
293+
acctestutil.TestEntPreCheck(t)
294+
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion210)
295+
},
296+
CheckDestroy: testCheckMountDestroyed(resourceType, consts.MountTypeLDAP, consts.FieldMount),
297+
Steps: []resource.TestStep{
298+
{
299+
Config: testLDAPSecretBackendStaticRoleConfig_rotateOnRead(path, bindDN, bindPass, url, username, dn, "true", "60"),
300+
Check: resource.ComposeTestCheckFunc(
301+
resource.TestCheckResourceAttr(resourceName, consts.FieldUsername, username),
302+
resource.TestCheckResourceAttr(resourceName, consts.FieldDN, dn),
303+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnRead, "true"),
304+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnReadCooldown, "60"),
305+
),
306+
},
307+
{
308+
Config: testLDAPSecretBackendStaticRoleConfig_rotateOnRead(path, bindDN, bindPass, url, username, dn, "true", "120"),
309+
Check: resource.ComposeTestCheckFunc(
310+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnRead, "true"),
311+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnReadCooldown, "120"),
312+
),
313+
},
314+
testutil.GetImportTestStep(resourceName, false, nil,
315+
consts.FieldMount, consts.FieldRoleName, consts.FieldSkipImportRotation),
316+
},
317+
})
318+
}
319+
320+
func testLDAPSecretBackendStaticRoleConfig_rotateOnRead(mount, bindDN, bindPass, url, username, dn, rotateOnRead, cooldown string) string {
321+
return fmt.Sprintf(`
322+
resource "vault_ldap_secret_backend" "test" {
323+
path = "%s"
324+
binddn = "%s"
325+
bindpass = "%s"
326+
url = "%s"
327+
userdn = "CN=Users,DC=corp,DC=example,DC=net"
328+
}
329+
330+
resource "vault_ldap_secret_backend_static_role" "role" {
331+
mount = vault_ldap_secret_backend.test.path
332+
username = "%s"
333+
dn = "%s"
334+
role_name = "%s"
335+
rotation_period = 60
336+
rotate_on_read = %s
337+
rotate_on_read_cooldown = %s
338+
skip_import_rotation = true
339+
}
340+
`, mount, bindDN, bindPass, url, username, dn, username, rotateOnRead, cooldown)
341+
}

vault/resource_ldap_secret_backend_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,3 +487,53 @@ resource "vault_ldap_secret_backend" "test" {
487487
bindpass_wo_version = 1
488488
}`, path)
489489
}
490+
491+
func TestLDAPSecretBackend_RotateOnRead(t *testing.T) {
492+
var (
493+
path = acctest.RandomWithPrefix("tf-test-ldap")
494+
bindDN = "test-bind-dn"
495+
resourceType = "vault_ldap_secret_backend"
496+
resourceName = resourceType + ".test"
497+
)
498+
resource.Test(t, resource.TestCase{
499+
ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(context.Background(), t),
500+
PreCheck: func() {
501+
acctestutil.TestEntPreCheck(t)
502+
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion210)
503+
},
504+
PreventPostDestroyRefresh: true,
505+
CheckDestroy: testCheckMountDestroyed(resourceType, consts.MountTypeLDAP, consts.FieldPath),
506+
Steps: []resource.TestStep{
507+
{
508+
Config: testLDAPSecretBackendConfig_rotateOnRead(path, bindDN, "true", "120"),
509+
Check: resource.ComposeTestCheckFunc(
510+
resource.TestCheckResourceAttr(resourceName, consts.FieldPath, path),
511+
resource.TestCheckResourceAttr(resourceName, consts.FieldBindDN, bindDN),
512+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnRead, "true"),
513+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnReadCooldown, "120"),
514+
),
515+
},
516+
{
517+
Config: testLDAPSecretBackendConfig_rotateOnRead(path, bindDN, "true", "240"),
518+
Check: resource.ComposeTestCheckFunc(
519+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnRead, "true"),
520+
resource.TestCheckResourceAttr(resourceName, consts.FieldRotateOnReadCooldown, "240"),
521+
),
522+
},
523+
testutil.GetImportTestStep(resourceName, false, nil, consts.FieldDisableRemount),
524+
},
525+
})
526+
}
527+
528+
func testLDAPSecretBackendConfig_rotateOnRead(path, bindDN, rotateOnRead, cooldown string) string {
529+
return fmt.Sprintf(`
530+
resource "vault_ldap_secret_backend" "test" {
531+
path = "%s"
532+
description = "test description"
533+
binddn = "%s"
534+
userdn = "CN=Users,DC=corp,DC=example,DC=net"
535+
self_managed = true
536+
rotate_on_read = %s
537+
rotate_on_read_cooldown = %s
538+
}`, path, bindDN, rotateOnRead, cooldown)
539+
}

website/docs/r/ldap_secret_backend.html.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ The following arguments are supported:
106106
using its current password (no privileged bind DN). Immutable after creation. Enforces `password`
107107
requirement when creating static roles. Requires Vault Enterprise 2.0+.
108108

109+
* `rotate_on_read` - (Optional) If true, static role credentials are rotated on each read. Acts as
110+
the default for all static roles that do not provide their own override. Requires Vault Enterprise 2.1.0+.
111+
112+
* `rotate_on_read_cooldown` - (Optional) Minimum number of seconds between rotate-on-read rotations.
113+
Acts as the default cooldown for all static roles that do not provide their own override.
114+
Requires Vault Enterprise 2.1.0+.
115+
109116
### Common Mount Arguments
110117
These arguments are common across all resources that mount a secret engine.
111118

website/docs/r/ldap_secret_backend_static_role.html.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ The following arguments are supported:
7777
* `password_policy` - (Optional) Name of the password policy to use to generate passwords for this role.
7878
Requires Vault 2.1.0+.
7979

80+
* `rotate_on_read` - (Optional) If true, credentials are rotated on each read. When set, this overrides
81+
the engine-level `rotate_on_read` default. When unset, the role inherits the engine-level value.
82+
Removing this attribute after it has been set is not supported without recreating the role.
83+
Requires Vault Enterprise 2.1.0+.
84+
85+
* `rotate_on_read_cooldown` - (Optional) Minimum number of seconds between rotate-on-read rotations
86+
for this role. When set, this overrides the engine-level `rotate_on_read_cooldown` default.
87+
When unset, the role inherits the engine-level value.
88+
Removing this attribute after it has been set is not supported without recreating the role.
89+
Requires Vault Enterprise 2.1.0+.
90+
8091
* `disable_automated_rotation` - (Optional) Cancels all upcoming rotations of the static credential until unset. Requires Vault Enterprise 2.0+.
8192

8293
* `password_wo_version` - (Optional) The version of the `password_wo`. For more info see [updating write-only attributes](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/guides/using_write_only_attributes.html#updating-write-only-attributes).

0 commit comments

Comments
 (0)