Skip to content

Commit 271ef2f

Browse files
authored
Retain certs when posting tls secrets (#189)
* Retain tls.crt & ca.crt when redacting secret data Certificate data is needed to make some of our recommendations in the cert-manager package. Signed-off-by: Charlie Egan <[email protected]> * Document secret redaction process Signed-off-by: Charlie Egan <[email protected]>
1 parent 5aa46d6 commit 271ef2f

File tree

3 files changed

+83
-10
lines changed

3 files changed

+83
-10
lines changed

docs/datagatherers/k8s-dynamic.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,28 @@ resource referenced in the `kind` for that datagatherer.
6363

6464
There is an example `ClusterRole` and `ClusterRoleBinding` which can be found in
6565
[`./deployment/kubernetes/base/00-rbac.yaml`](./deployment/kubernetes/base/00-rbac.yaml).
66+
67+
# Secrets
68+
69+
Secrets can be gathered using the following config:
70+
71+
```yaml
72+
- kind: "k8s-dynamic"
73+
name: "k8s/secrets"
74+
config:
75+
resource-type:
76+
version: v1
77+
resource: secrets
78+
```
79+
80+
Before Secrets are sent to the Preflight backend, they are redacted in the
81+
following way:
82+
83+
- `last-applied-configuration` annotation is removed
84+
- For Secrets of type `kubernetes.io/tls`
85+
- All keys under data other than the following are removed:
86+
- tls.crt
87+
- ca.crt
88+
- All other secrets have all keys removed from their data.
89+
90+
**All resource other than Kubernetes Secrets are sent in full.**

pkg/datagatherer/k8s/dynamic.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ func redactList(list *unstructured.UnstructuredList) error {
158158
// In principal we could only redact the list if it's kind is SecretList or
159159
// a generic mixed List, however the test suite does not set the list kind
160160
// and it is safer to always check for Secrets.
161-
// Iterate over the items in the list.
162161
for i := range list.Items {
163162
// Determine the kind of items in case this is a generic 'mixed' list.
164163
gvks, _, err := scheme.Scheme.ObjectKinds(&list.Items[i])
@@ -168,17 +167,38 @@ func redactList(list *unstructured.UnstructuredList) error {
168167
for _, gvk := range gvks {
169168
// If this item is a Secret then we need to redact it.
170169
if gvk.Kind == "Secret" && (gvk.Group == "core" || gvk.Group == "") {
171-
// Redact secret data
172-
list.Items[i].Object["data"] = map[string]interface{}{}
170+
secret := list.Items[i]
171+
172+
// If the secret is a tls secret, we redact all data other then
173+
// the tls.crt and ca.crt. This is because we need to inspect
174+
// the certificate to make recommendations.
175+
if secret.Object["type"] == "kubernetes.io/tls" {
176+
secretData, ok := secret.Object["data"].(map[string]interface{})
177+
if ok {
178+
for k := range secretData {
179+
// Only these two keys will be sent, all others are
180+
// deleted
181+
if k != "tls.crt" && k != "ca.crt" {
182+
delete(secretData, k)
183+
}
184+
}
185+
} else {
186+
// If secret is not string mapping, redact all secret data
187+
secret.Object["data"] = map[string]interface{}{}
188+
}
189+
} else {
190+
// Redact all secret data for non-tls secrets
191+
secret.Object["data"] = map[string]interface{}{}
192+
}
173193

174194
// Redact last-applied-configuration annotation if set
175-
annotations, present := list.Items[i].Object["annotations"].(map[string]interface{})
195+
annotations, present := secret.Object["annotations"].(map[string]interface{})
176196
if present {
177197
_, annotationPresent := annotations["kubectl.kubernetes.io/last-applied-configuration"]
178198
if annotationPresent {
179199
annotations["kubectl.kubernetes.io/last-applied-configuration"] = "redacted"
180200
}
181-
list.Items[i].Object["annotations"] = annotations
201+
secret.Object["annotations"] = annotations
182202
}
183203
break
184204
}

pkg/datagatherer/k8s/dynamic_test.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ func getObject(version, kind, name, namespace string) *unstructured.Unstructured
2727
}
2828
}
2929

30-
func getSecret(name, namespace string, data map[string]interface{}, withLastApplied bool) *unstructured.Unstructured {
30+
func getSecret(name, namespace string, data map[string]interface{}, isTLS bool, withLastApplied bool) *unstructured.Unstructured {
3131
object := getObject("v1", "Secret", name, namespace)
3232
object.Object["data"] = data
3333

34+
object.Object["type"] = "Opaque"
35+
if isTLS {
36+
object.Object["type"] = "kubernetes.io/tls"
37+
}
38+
3439
// if we're creating a 'raw' secret as scraped that was applied by kubectl
3540
if withLastApplied {
3641
jsonData, _ := json.Marshal(data)
@@ -137,14 +142,37 @@ func TestDynamicGatherer_Fetch(t *testing.T) {
137142
objects: []runtime.Object{
138143
getSecret("testsecret", "testns", map[string]interface{}{
139144
"secretKey": "secretValue",
140-
}, true),
145+
}, false, true),
141146
getSecret("anothertestsecret", "differentns", map[string]interface{}{
142147
"secretNumber": "12345",
143-
}, true),
148+
}, false, true),
144149
},
145150
expected: asUnstructuredList(
146-
getSecret("testsecret", "testns", map[string]interface{}{}, false),
147-
getSecret("anothertestsecret", "differentns", map[string]interface{}{}, false),
151+
getSecret("testsecret", "testns", map[string]interface{}{}, false, false),
152+
getSecret("anothertestsecret", "differentns", map[string]interface{}{}, false, false),
153+
),
154+
},
155+
"Secret of type kubernetes.io/tls should have crts and not keys": {
156+
gvr: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"},
157+
objects: []runtime.Object{
158+
getSecret("testsecret", "testns", map[string]interface{}{
159+
"tls.key": "secretValue",
160+
"tls.crt": "value",
161+
"ca.crt": "value",
162+
}, true, true),
163+
getSecret("anothertestsecret", "differentns", map[string]interface{}{
164+
"example.key": "secretValue",
165+
"example.crt": "value",
166+
}, true, true),
167+
},
168+
expected: asUnstructuredList(
169+
// only tls.crt and ca.cert remain
170+
getSecret("testsecret", "testns", map[string]interface{}{
171+
"tls.crt": "value",
172+
"ca.crt": "value",
173+
}, true, false),
174+
// all other keys removed
175+
getSecret("anothertestsecret", "differentns", map[string]interface{}{}, true, false),
148176
),
149177
},
150178
"Foos in different namespaces should be returned if they are in the namespace list for the gatherer": {

0 commit comments

Comments
 (0)