Skip to content

Commit 9055bfc

Browse files
refactor(cyberark): update Snapshot resource types to client.Object
This commit refactors the `Snapshot` resource types to use `client.Object` instead of `runtime.Object`, aiming to improve type safety. This reduces the amount of type casting and associated error handling in the minimizeSnapshot code and allows the logging of dropped secret name and namespace becasue the secret objects are known to satisfy both metav1.Object and runtime.Object. The extractResourceListFromReading function needed to be updated accordingly to handle generic types. - Replace runtime.Object with client.Object and *unstructured.Unstructured in Snapshot struct and related functions - Refactor minimizeSnapshot and isExcludableSecret to use new types - Update extractResourceListFromReading to use generics for resource type - Adjust tests to match new resource type signatures and expectations - Add "type" field to example Secret in input.json for clarity Signed-off-by: Richard Wall <[email protected]>
1 parent 56dbef5 commit 9055bfc

File tree

4 files changed

+73
-78
lines changed

4 files changed

+73
-78
lines changed

examples/machinehub/input.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"metadata": {
2020
"name": "app-1-secret-1",
2121
"namespace": "team-1"
22-
}
22+
},
23+
"type": "kubernetes.io/tls"
2324
}
2425
}
2526
]

internal/cyberark/dataupload/dataupload.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
"net/http"
1313
"net/url"
1414

15-
"k8s.io/apimachinery/pkg/runtime"
15+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
1617

1718
"github.com/jetstack/preflight/pkg/version"
1819
)
@@ -54,29 +55,32 @@ type Snapshot struct {
5455
K8SVersion string `json:"k8s_version"`
5556
// Secrets is a list of Secret resources in the cluster. Not all Secret
5657
// types are included and only a subset of the Secret data is included.
57-
Secrets []runtime.Object `json:"secrets"`
58+
//
59+
// Secrets are obtained by a DynamicClient, so they have type
60+
// *unstructured.Unstructured.
61+
Secrets []*unstructured.Unstructured `json:"secrets"`
5862
// ServiceAccounts is a list of ServiceAccount resources in the cluster.
59-
ServiceAccounts []runtime.Object `json:"serviceaccounts"`
63+
ServiceAccounts []client.Object `json:"serviceaccounts"`
6064
// Roles is a list of Role resources in the cluster.
61-
Roles []runtime.Object `json:"roles"`
65+
Roles []client.Object `json:"roles"`
6266
// ClusterRoles is a list of ClusterRole resources in the cluster.
63-
ClusterRoles []runtime.Object `json:"clusterroles"`
67+
ClusterRoles []client.Object `json:"clusterroles"`
6468
// RoleBindings is a list of RoleBinding resources in the cluster.
65-
RoleBindings []runtime.Object `json:"rolebindings"`
69+
RoleBindings []client.Object `json:"rolebindings"`
6670
// ClusterRoleBindings is a list of ClusterRoleBinding resources in the cluster.
67-
ClusterRoleBindings []runtime.Object `json:"clusterrolebindings"`
71+
ClusterRoleBindings []client.Object `json:"clusterrolebindings"`
6872
// Jobs is a list of Job resources in the cluster.
69-
Jobs []runtime.Object `json:"jobs"`
73+
Jobs []client.Object `json:"jobs"`
7074
// CronJobs is a list of CronJob resources in the cluster.
71-
CronJobs []runtime.Object `json:"cronjobs"`
75+
CronJobs []client.Object `json:"cronjobs"`
7276
// Deployments is a list of Deployment resources in the cluster.
73-
Deployments []runtime.Object `json:"deployments"`
77+
Deployments []client.Object `json:"deployments"`
7478
// Statefulsets is a list of StatefulSet resources in the cluster.
75-
Statefulsets []runtime.Object `json:"statefulsets"`
79+
Statefulsets []client.Object `json:"statefulsets"`
7680
// Daemonsets is a list of DaemonSet resources in the cluster.
77-
Daemonsets []runtime.Object `json:"daemonsets"`
81+
Daemonsets []client.Object `json:"daemonsets"`
7882
// Pods is a list of Pod resources in the cluster.
79-
Pods []runtime.Object `json:"pods"`
83+
Pods []client.Object `json:"pods"`
8084
}
8185

8286
// PutSnapshot PUTs the supplied snapshot to an [AWS presigned URL] which it obtains via the CyberArk inventory API.

pkg/client/client_cyberark.go

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import (
1111
"github.com/go-logr/logr"
1212
corev1 "k8s.io/api/core/v1"
1313
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14-
"k8s.io/apimachinery/pkg/runtime"
1514
"k8s.io/apimachinery/pkg/util/sets"
1615
"k8s.io/klog/v2"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
1717

1818
"github.com/jetstack/preflight/api"
1919
"github.com/jetstack/preflight/internal/cyberark"
@@ -60,7 +60,7 @@ func (o *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, readin
6060
}
6161

6262
// Minimize the snapshot to reduce size and improve privacy
63-
minimizeSnapshot(log.V(logs.Debug), &snapshot)
63+
minimizeSnapshot(log.V(logs.Debug).WithName("minimizeSnapshot"), &snapshot)
6464

6565
snapshot.AgentVersion = version.PreflightVersion
6666

@@ -100,9 +100,9 @@ func extractClusterIDAndServerVersionFromReading(reading *api.DataReading, targe
100100
}
101101

102102
// extractResourceListFromReading converts the opaque data from a DynamicData
103-
// data reading to runtime.Object resources, to allow access to the metadata and
103+
// data reading to T resources, to allow access to the metadata and
104104
// other kubernetes API fields.
105-
func extractResourceListFromReading(reading *api.DataReading, target *[]runtime.Object) error {
105+
func extractResourceListFromReading[T client.Object](reading *api.DataReading, target *[]T) error {
106106
if reading == nil {
107107
return fmt.Errorf("programmer mistake: the DataReading must not be nil")
108108
}
@@ -112,14 +112,15 @@ func extractResourceListFromReading(reading *api.DataReading, target *[]runtime.
112112
"programmer mistake: the DataReading must have data type *api.DynamicData. "+
113113
"This DataReading (%s) has data type %T", reading.DataGatherer, reading.Data)
114114
}
115-
resources := make([]runtime.Object, len(data.Items))
115+
resources := make([]T, len(data.Items))
116116
for i, item := range data.Items {
117-
if resource, ok := item.Resource.(runtime.Object); ok {
117+
if resource, ok := item.Resource.(T); ok {
118118
resources[i] = resource
119119
} else {
120+
expectedType := fmt.Sprintf("%T", new(T))[1:] // strip leading '*'
120121
return fmt.Errorf(
121-
"programmer mistake: the DynamicData items must have Resource type runtime.Object. "+
122-
"This item (%d) has Resource type %T", i, item.Resource)
122+
"programmer mistake: the DynamicData items must have Resource type %s. "+
123+
"This item (%d) has Resource type %T", expectedType, i, item.Resource)
123124
}
124125
}
125126
*target = resources
@@ -223,9 +224,11 @@ func convertDataReadings(
223224
// service.
224225
func minimizeSnapshot(log logr.Logger, snapshot *dataupload.Snapshot) {
225226
originalSecretCount := len(snapshot.Secrets)
226-
filteredSecrets := make([]runtime.Object, 0, originalSecretCount)
227+
filteredSecrets := make([]*unstructured.Unstructured, 0, originalSecretCount)
227228
for _, secret := range snapshot.Secrets {
229+
log := log.WithValues("name", secret.GetName(), "namespace", secret.GetNamespace())
228230
if isExcludableSecret(log, secret) {
231+
log.Info("Dropped")
229232
continue
230233
}
231234
filteredSecrets = append(filteredSecrets, secret)
@@ -240,24 +243,9 @@ func minimizeSnapshot(log logr.Logger, snapshot *dataupload.Snapshot) {
240243
//
241244
// The Secret is kept if there is any doubt or if there is a problem decoding
242245
// its contents.
243-
//
244-
// Secrets are obtained by a DynamicClient, so they have type
245-
// *unstructured.Unstructured.
246-
func isExcludableSecret(log logr.Logger, obj runtime.Object) bool {
247-
// Fast path: type assertion and kind/type checks
248-
unstructuredObj, ok := obj.(*unstructured.Unstructured)
249-
if !ok {
250-
log.Info("Object is not a Unstructured", "type", fmt.Sprintf("%T", obj))
251-
return false
252-
}
246+
func isExcludableSecret(log logr.Logger, unstructuredObj *unstructured.Unstructured) bool {
253247
if unstructuredObj.GetKind() != "Secret" || unstructuredObj.GetAPIVersion() != "v1" {
254-
return false
255-
}
256-
257-
log = log.WithValues("namespace", unstructuredObj.GetNamespace(), "name", unstructuredObj.GetName())
258-
dataMap, found, err := unstructured.NestedMap(unstructuredObj.Object, "data")
259-
if err != nil || !found {
260-
log.Info("Secret data missing or not a map")
248+
log.Info("Object is not a core/v1 Secret", "apiVersion", unstructuredObj.GetAPIVersion(), "kind", unstructuredObj.GetKind())
261249
return false
262250
}
263251

@@ -268,10 +256,16 @@ func isExcludableSecret(log logr.Logger, obj runtime.Object) bool {
268256
}
269257

270258
if corev1.SecretType(secretType) != corev1.SecretTypeTLS {
271-
log.Info("Secret of this type are never excluded", "type", secretType)
259+
log.Info("Secret of this type are never dropped", "type", secretType)
272260
return false
273261
}
274262

263+
dataMap, found, err := unstructured.NestedMap(unstructuredObj.Object, "data")
264+
if err != nil || !found {
265+
log.Info("Secret data missing or not a map", "error", err, "decision", "drop")
266+
return true
267+
}
268+
275269
return isExcludableTLSSecret(log, dataMap)
276270
}
277271

pkg/client/client_cyberark_convertdatareadings_test.go

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ import (
1414

1515
"github.com/stretchr/testify/assert"
1616
"github.com/stretchr/testify/require"
17-
corev1 "k8s.io/api/core/v1"
18-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1917
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
20-
"k8s.io/apimachinery/pkg/runtime"
2118
"k8s.io/apimachinery/pkg/version"
2219
"k8s.io/klog/v2/ktesting"
20+
"sigs.k8s.io/controller-runtime/pkg/client"
2321

2422
"github.com/jetstack/preflight/api"
2523
"github.com/jetstack/preflight/internal/cyberark/dataupload"
@@ -155,7 +153,7 @@ func TestExtractResourceListFromReading(t *testing.T) {
155153
},
156154
},
157155
},
158-
expectError: `programmer mistake: the DynamicData items must have Resource type runtime.Object. ` +
156+
expectError: `programmer mistake: the DynamicData items must have Resource type *unstructured.Unstructured. ` +
159157
`This item (0) has Resource type *api.DiscoveryData`,
160158
},
161159
{
@@ -194,7 +192,7 @@ func TestExtractResourceListFromReading(t *testing.T) {
194192
}
195193
for _, test := range tests {
196194
t.Run(test.name, func(t *testing.T) {
197-
var resources []runtime.Object
195+
var resources []*unstructured.Unstructured
198196
err := extractResourceListFromReading(test.reading, &resources)
199197
if test.expectError != "" {
200198
assert.EqualError(t, err, test.expectError)
@@ -231,10 +229,14 @@ func TestConvertDataReadings(t *testing.T) {
231229
Data: &api.DynamicData{
232230
Items: []*api.GatheredResource{
233231
{
234-
Resource: &corev1.Secret{
235-
ObjectMeta: metav1.ObjectMeta{
236-
Name: "app-1",
237-
Namespace: "team-1",
232+
Resource: &unstructured.Unstructured{
233+
Object: map[string]interface{}{
234+
"apiVersion": "v1",
235+
"kind": "Secret",
236+
"metadata": map[string]interface{}{
237+
"name": "app-1",
238+
"namespace": "team-1",
239+
},
238240
},
239241
},
240242
},
@@ -293,11 +295,15 @@ func TestConvertDataReadings(t *testing.T) {
293295
expectedSnapshot: dataupload.Snapshot{
294296
ClusterID: "success-cluster-id",
295297
K8SVersion: "v1.21.0",
296-
Secrets: []runtime.Object{
297-
&corev1.Secret{
298-
ObjectMeta: metav1.ObjectMeta{
299-
Name: "app-1",
300-
Namespace: "team-1",
298+
Secrets: []*unstructured.Unstructured{
299+
{
300+
Object: map[string]interface{}{
301+
"apiVersion": "v1",
302+
"kind": "Secret",
303+
"metadata": map[string]interface{}{
304+
"name": "app-1",
305+
"namespace": "team-1",
306+
},
301307
},
302308
},
303309
},
@@ -351,17 +357,17 @@ func TestMinimizeSnapshot(t *testing.T) {
351357
AgentVersion: "v1.0.0",
352358
ClusterID: "cluster-1",
353359
K8SVersion: "v1.21.0",
354-
Secrets: []runtime.Object{},
355-
ServiceAccounts: []runtime.Object{},
356-
Roles: []runtime.Object{},
360+
Secrets: []*unstructured.Unstructured{},
361+
ServiceAccounts: []client.Object{},
362+
Roles: []client.Object{},
357363
},
358364
expectedSnapshot: dataupload.Snapshot{
359365
AgentVersion: "v1.0.0",
360366
ClusterID: "cluster-1",
361367
K8SVersion: "v1.21.0",
362-
Secrets: []runtime.Object{},
363-
ServiceAccounts: []runtime.Object{},
364-
Roles: []runtime.Object{},
368+
Secrets: []*unstructured.Unstructured{},
369+
ServiceAccounts: []client.Object{},
370+
Roles: []client.Object{},
365371
},
366372
},
367373
{
@@ -370,28 +376,28 @@ func TestMinimizeSnapshot(t *testing.T) {
370376
AgentVersion: "v1.0.0",
371377
ClusterID: "cluster-1",
372378
K8SVersion: "v1.21.0",
373-
Secrets: []runtime.Object{
379+
Secrets: []*unstructured.Unstructured{
374380
secretWithClientCert,
375381
secretWithoutClientCert,
376382
opaqueSecret,
377383
},
378-
ServiceAccounts: []runtime.Object{
384+
ServiceAccounts: []client.Object{
379385
serviceAccount,
380386
},
381-
Roles: []runtime.Object{},
387+
Roles: []client.Object{},
382388
},
383389
expectedSnapshot: dataupload.Snapshot{
384390
AgentVersion: "v1.0.0",
385391
ClusterID: "cluster-1",
386392
K8SVersion: "v1.21.0",
387-
Secrets: []runtime.Object{
393+
Secrets: []*unstructured.Unstructured{
388394
secretWithClientCert,
389395
opaqueSecret,
390396
},
391-
ServiceAccounts: []runtime.Object{
397+
ServiceAccounts: []client.Object{
392398
serviceAccount,
393399
},
394-
Roles: []runtime.Object{},
400+
Roles: []client.Object{},
395401
},
396402
},
397403
}
@@ -408,7 +414,7 @@ func TestMinimizeSnapshot(t *testing.T) {
408414
func TestIsExcludableSecret(t *testing.T) {
409415
type testCase struct {
410416
name string
411-
secret runtime.Object
417+
secret *unstructured.Unstructured
412418
exclude bool
413419
}
414420

@@ -423,16 +429,6 @@ func TestIsExcludableSecret(t *testing.T) {
423429
secret: newTLSSecret("tls-secret-without-client", sampleCertificateChain(t, x509.ExtKeyUsageServerAuth)),
424430
exclude: true,
425431
},
426-
{
427-
name: "Non-unstructured",
428-
secret: &corev1.Pod{
429-
ObjectMeta: metav1.ObjectMeta{
430-
Name: "non-unstructured-secret",
431-
Namespace: "default",
432-
},
433-
},
434-
exclude: false,
435-
},
436432
{
437433
name: "Non-secret",
438434
secret: &unstructured.Unstructured{

0 commit comments

Comments
 (0)