Skip to content

Commit

Permalink
support for db schedule-based static role rotations
Browse files Browse the repository at this point in the history
  • Loading branch information
fairclothjm committed Sep 20, 2023
1 parent 6f67158 commit da3530a
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 9 deletions.
5 changes: 5 additions & 0 deletions api/v1beta1/vaultdynamicsecret_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ type VaultStaticCredsMetaData struct {
// "time to live". This value is compared to the LastVaultRotation to
// determine if a password needs to be rotated
RotationPeriod int64 `json:"rotationPeriod"`
// RotationSchedule is a "chron style" string representing the allowed
// schedule for each rotation.
// e.g. "1 0 * * *" would rotate at one minute past midnight (00:01) every
// day.
RotationSchedule string `json:"rotationSchedule"`
// TTL is the seconds remaining before the next rotation.
TTL int64 `json:"ttl"`
}
Expand Down
6 changes: 6 additions & 0 deletions chart/crds/secrets.hashicorp.com_vaultdynamicsecrets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,19 @@ spec:
be rotated
format: int64
type: integer
rotationSchedule:
description: RotationSchedule is a "chron style" string representing
the allowed schedule for each rotation. e.g. "1 0 * * *" would
rotate at one minute past midnight (00:01) every day.
type: string
ttl:
description: TTL is the seconds remaining before the next rotation.
format: int64
type: integer
required:
- lastVaultRotation
- rotationPeriod
- rotationSchedule
- ttl
type: object
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,19 @@ spec:
be rotated
format: int64
type: integer
rotationSchedule:
description: RotationSchedule is a "chron style" string representing
the allowed schedule for each rotation. e.g. "1 0 * * *" would
rotate at one minute past midnight (00:01) every day.
type: string
ttl:
description: TTL is the seconds remaining before the next rotation.
format: int64
type: integer
required:
- lastVaultRotation
- rotationPeriod
- rotationSchedule
- ttl
type: object
required:
Expand Down
7 changes: 6 additions & 1 deletion controllers/vaultdynamicsecret_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func (r *VaultDynamicSecretReconciler) isRenewableLease(secretLease *secretsv1be
func (r *VaultDynamicSecretReconciler) isStaticCreds(meta *secretsv1beta1.VaultStaticCredsMetaData) bool {
// the ldap and database engines have minimum rotation period of 5s, requiring a
// minimum of 1s should be okay here.
return meta.LastVaultRotation > 0 && meta.RotationPeriod > 1
return meta.LastVaultRotation > 0 && (meta.RotationPeriod > 1 || meta.RotationSchedule != "")
}

func (r *VaultDynamicSecretReconciler) syncSecret(ctx context.Context, c vault.ClientBase, o *secretsv1beta1.VaultDynamicSecret) (*secretsv1beta1.VaultSecretLease, bool, error) {
Expand Down Expand Up @@ -332,6 +332,11 @@ func (r *VaultDynamicSecretReconciler) syncSecret(ctx context.Context, c vault.C
}
}
}
if v, ok := respData["rotation_schedule"]; ok && v != nil {
if schedule, ok := v.(string); ok && v != nil {
o.Status.StaticCredsMetaData.RotationSchedule = schedule
}
}
if v, ok := respData["ttl"]; ok && v != nil {
switch t := v.(type) {
case json.Number:
Expand Down
1 change: 1 addition & 0 deletions docs/api/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ _Appears in:_
| --- | --- |
| `lastVaultRotation` _integer_ | LastVaultRotation represents the last time Vault rotated the password |
| `rotationPeriod` _integer_ | RotationPeriod is number in seconds between each rotation, effectively a "time to live". This value is compared to the LastVaultRotation to determine if a password needs to be rotated |
| `rotationSchedule` _string_ | RotationSchedule is a "chron style" string representing the allowed schedule for each rotation. e.g. "1 0 * * *" would rotate at one minute past midnight (00:01) every day. |
| `ttl` _integer_ | TTL is the seconds remaining before the next rotation. |


Expand Down
2 changes: 1 addition & 1 deletion test/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ type dynamicK8SOutputs struct {
}

func assertDynamicSecret(t *testing.T, client ctrlclient.Client, maxRetries int,
delay time.Duration, vdsObj *secretsv1beta1.VaultDynamicSecret, expected map[string]int,
delay time.Duration, vdsObj *secretsv1beta1.VaultDynamicSecret, expected map[string]interface{},
) {
t.Helper()

Expand Down
45 changes: 38 additions & 7 deletions test/integration/vaultdynamicsecret_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ func TestVaultDynamicSecret(t *testing.T) {
tests := []struct {
name string
authObj *secretsv1beta1.VaultAuth
expected map[string]int
expectedStatic map[string]int
expected map[string]interface{}
expectedStatic map[string]interface{}
create int
createStatic int
existing []string
Expand All @@ -199,7 +199,7 @@ func TestVaultDynamicSecret(t *testing.T) {
name: "existing-only",
existing: outputs.K8sDBSecrets,
authObj: auths[0],
expected: map[string]int{
expected: map[string]interface{}{
helpers.SecretDataKeyRaw: 100,
"username": 51,
"password": 20,
Expand All @@ -209,7 +209,7 @@ func TestVaultDynamicSecret(t *testing.T) {
name: "create-only",
create: 5,
authObj: auths[0],
expected: map[string]int{
expected: map[string]interface{}{
helpers.SecretDataKeyRaw: 100,
"username": 51,
"password": 20,
Expand All @@ -221,12 +221,12 @@ func TestVaultDynamicSecret(t *testing.T) {
createStatic: 5,
existing: outputs.K8sDBSecrets,
authObj: auths[0],
expected: map[string]int{
expected: map[string]interface{}{
helpers.SecretDataKeyRaw: 100,
"username": 51,
"password": 20,
},
expectedStatic: map[string]int{
expectedStatic: map[string]interface{}{
// the _raw, last_vault_rotation, and ttl keys are only tested for their presence in
// assertDynamicSecret, so no need to include them here.
"password": 20,
Expand All @@ -238,14 +238,45 @@ func TestVaultDynamicSecret(t *testing.T) {
name: "create-static",
createStatic: 5,
authObj: auths[0],
expectedStatic: map[string]int{
expectedStatic: map[string]interface{}{
// the _raw, last_vault_rotation, and ttl keys are only tested for their presence in
// assertDynamicSecret, so no need to include them here.
"password": 20,
"rotation_period": 2,
"username": 24,
},
},
{
name: "mixed-rotation-schedule",
create: 5,
createStatic: 5,
existing: outputs.K8sDBSecrets,
authObj: auths[0],
expected: map[string]interface{}{
helpers.SecretDataKeyRaw: 100,
"username": 51,
"password": 20,
},
expectedStatic: map[string]interface{}{
// the _raw, last_vault_rotation, and ttl keys are only tested for their presence in
// assertDynamicSecret, so no need to include them here.
"password": 20,
"rotation_schedule": "0 0 * * SAT",
"username": 24,
},
},
{
name: "create-static-rotation-schedule",
createStatic: 5,
authObj: auths[0],
expectedStatic: map[string]interface{}{
// the _raw, last_vault_rotation, and ttl keys are only tested for their presence in
// assertDynamicSecret, so no need to include them here.
"password": 20,
"rotation_schedule": "0 0 * * SAT",
"username": 24,
},
},
}

for _, tt := range tests {
Expand Down

0 comments on commit da3530a

Please sign in to comment.