Skip to content

Commit 3340526

Browse files
Merge pull request #252 from davoustp/main
#196 RandomSecret retention policy attribute and logic Including one-off existing Vault secret overwrite protection
2 parents 14e3ed6 + 0c7352f commit 3340526

File tree

5 files changed

+63
-0
lines changed

5 files changed

+63
-0
lines changed

api/v1alpha1/randomsecret_types.go

+10
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,20 @@ type RandomSecretSpec struct {
8080
// +kubebuilder:validation:Optional
8181
// +kubebuilder:validation:Pattern:=`[a-z0-9]([-a-z0-9]*[a-z0-9])?`
8282
Name string `json:"name,omitempty"`
83+
84+
// The KV secret retain policy to apply when the Kubernetes resource is deleted.
85+
// When unspecified, the KV secret is also deleted.
86+
// +kubebuilder:validation:Optional
87+
// +kubebuilder:validation:Enum:={"Delete","Retain"}
88+
// +kubebuilder:default:="Delete"
89+
KvSecretRetainPolicy string `json:"kvSecretRetainPolicy,omitempty"`
8390
}
8491

8592
const ttlKey string = "ttl"
8693

94+
const RetainKvSecretRetainPolicy = "Retain"
95+
const DeleteKvSecretRetainPolicy = "Delete"
96+
8797
var _ vaultutils.VaultObject = &RandomSecret{}
8898
var _ vaultutils.ConditionsAware = &RandomSecret{}
8999

api/v1alpha1/utils/vaultobject.go

+10
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ func (ve *VaultEndpoint) DeleteIfExists(context context.Context) error {
8787
return nil
8888
}
8989

90+
func (ve *VaultEndpoint) Exists(context context.Context) (bool, error) {
91+
log := log.FromContext(context)
92+
_, found, err := read(context, ve.vaultObject.GetPath())
93+
if err != nil {
94+
log.Error(err, "unable to check object existence at", "path", ve.vaultObject.GetPath())
95+
return false, err
96+
}
97+
return found, nil
98+
}
99+
90100
func (ve *VaultEndpoint) Create(context context.Context) error {
91101
return write(context, ve.vaultObject.GetPath(), ve.vaultObject.GetPayload())
92102
}

config/crd/bases/redhatcop.redhat.io_randomsecrets.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ spec:
124124
is V2 or not. Default is false to indicate the payload to send is
125125
for KV Secret Engine V1.
126126
type: boolean
127+
kvSecretRetainPolicy:
128+
default: Delete
129+
description: The KV secret retain policy to apply when the Kubernetes
130+
resource is deleted. When unspecified, the KV secret is also deleted.
131+
enum:
132+
- Delete
133+
- Retain
134+
type: string
127135
name:
128136
description: The name of the obejct created in Vault. If this is specified
129137
it takes precedence over {metatada.name}

controllers/randomsecret_controller.go

+17
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ func (r *RandomSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request
123123
}
124124

125125
func (r *RandomSecretReconciler) manageCleanUpLogic(context context.Context, instance *redhatcopv1alpha1.RandomSecret) error {
126+
if instance.Spec.KvSecretRetainPolicy == redhatcopv1alpha1.RetainKvSecretRetainPolicy {
127+
return nil
128+
}
129+
126130
vaultEndpoint := vaultutils.NewVaultEndpoint(instance)
127131

128132
if instance.IsKVSecretsEngineV2() {
@@ -147,6 +151,19 @@ func (r *RandomSecretReconciler) manageReconcileLogic(context context.Context, i
147151
return nil
148152
}
149153
vaultEndpoint := vaultutils.NewVaultEndpoint(instance)
154+
// When this is a newly created RandomSecret and no refresh period is defined (= one-off random password)
155+
// check that the Vault KV secret does not exist already to avoid overwriting its existing value.
156+
if instance.Status.LastVaultSecretUpdate == nil && instance.Spec.RefreshPeriod == nil {
157+
found, err := vaultEndpoint.Exists(context)
158+
if err != nil {
159+
r.Log.Error(err, "unable to verify secret existence", "instance", instance)
160+
return err
161+
}
162+
if found {
163+
r.Log.Info("no refresh period is defined and Vault secret already exists - nothing to do", "name", instance.Name)
164+
return nil
165+
}
166+
}
150167
err := instance.PrepareInternalValues(context, instance)
151168
if err != nil {
152169
r.Log.Error(err, "unable to generate new secret", "instance", instance)

docs/secret-management.md

+18
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ This CR is roughly equivalent to this Vault CLI command:
4343
vault kv put [namespace/]kv/vault-tenant password=<generated value>
4444
```
4545

46+
### Retention policy on delete
47+
48+
Since v0.8.17, when a `RandomSecret` Kubernetes resource is deleted, then the corresponding KV secret is also entirely deleted
49+
from Vault (all versions of it when using using KVv2).
50+
51+
Since v0.8.30, this can be controlled using the optional `spec.kvSecretRetainPolicy` attribute,
52+
which possible values are `Delete` (default) or `Retain`.
53+
54+
When set to `Retain`, then the KV secret is *not* deleted from Vault when the `RandomSecret` resource is deleted: this allows to
55+
protect randomly-generated but important secrets from unexpected deletion, such as a root password as described above
56+
(which you don't want to loose if the Kubernetes `RandomSecret` was erroneously deleted).
57+
58+
In addition to this, starting with v0.8.30, when a `RandomSecret` is created without a `refreshPeriod`
59+
and with a corresponding *existing* Vault secret, this Vault secret will *not* be updated:
60+
this is intended to provide overwrite protection for Kubernetes recreate-after-delete actions
61+
and again avoid loosing the initially generated secret value.
62+
63+
4664
## VaultSecret
4765

4866
The VaultSecret CRD allows a user to create a K8s Secret from one or more Vault Secrets. It uses go templating to allow formatting of the K8s Secret in the `output.stringData` section of the spec.

0 commit comments

Comments
 (0)