Skip to content

Commit 558fde0

Browse files
Merge pull request #741 from jetstack/cyberark-skip-deleted-resources
[VC-46370] CyberArk: Skip deleted resources when converting data readings to snapshot
2 parents 109ea6e + ca9ae2e commit 558fde0

File tree

4 files changed

+95
-2
lines changed

4 files changed

+95
-2
lines changed

examples/machinehub/input.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@
2121
"namespace": "team-1"
2222
}
2323
}
24+
},
25+
{
26+
"deleted_at": "2024-06-10T12:00:00Z",
27+
"resource": {
28+
"kind": "Secret",
29+
"apiVersion": "v1",
30+
"metadata": {
31+
"name": "deleted-secret-1",
32+
"namespace": "team-2"
33+
}
34+
}
2435
}
2536
]
2637
}
@@ -38,6 +49,17 @@
3849
"namespace": "team-1"
3950
}
4051
}
52+
},
53+
{
54+
"deleted_at": "2024-06-10T12:00:00Z",
55+
"resource": {
56+
"kind": "Pod",
57+
"apiVersion": "v1",
58+
"metadata": {
59+
"name": "deleted-pod-1",
60+
"namespace": "team-2"
61+
}
62+
}
4163
}
4264
]
4365
}

pkg/client/client_cyberark.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func NewCyberArk(httpClient *http.Client) (*CyberArkClient, error) {
4848

4949
// PostDataReadingsWithOptions uploads data readings to CyberArk.
5050
// It converts the supplied data readings into a snapshot format expected by CyberArk.
51+
// Deleted resources are excluded from the snapshot because they are not needed by CyberArk.
5152
// It then minimizes the snapshot to avoid uploading unnecessary data.
5253
// It initializes a data upload client with the configured HTTP client and credentials,
5354
// then uploads a snapshot.
@@ -112,6 +113,8 @@ func extractClusterIDAndServerVersionFromReading(reading *api.DataReading, targe
112113
// extractResourceListFromReading converts the opaque data from a DynamicData
113114
// data reading to runtime.Object resources, to allow access to the metadata and
114115
// other kubernetes API fields.
116+
// Deleted resources are skipped because the CyberArk Discovery and Context service
117+
// does not need to see resources that no longer exist.
115118
func extractResourceListFromReading(reading *api.DataReading, target *[]runtime.Object) error {
116119
if reading == nil {
117120
return fmt.Errorf("programmer mistake: the DataReading must not be nil")
@@ -122,10 +125,13 @@ func extractResourceListFromReading(reading *api.DataReading, target *[]runtime.
122125
"programmer mistake: the DataReading must have data type *api.DynamicData. "+
123126
"This DataReading (%s) has data type %T", reading.DataGatherer, reading.Data)
124127
}
125-
resources := make([]runtime.Object, len(data.Items))
128+
resources := make([]runtime.Object, 0, len(data.Items))
126129
for i, item := range data.Items {
130+
if !item.DeletedAt.IsZero() {
131+
continue
132+
}
127133
if resource, ok := item.Resource.(runtime.Object); ok {
128-
resources[i] = resource
134+
resources = append(resources, resource)
129135
} else {
130136
return fmt.Errorf(
131137
"programmer mistake: the DynamicData items must have Resource type runtime.Object. "+
@@ -136,6 +142,11 @@ func extractResourceListFromReading(reading *api.DataReading, target *[]runtime.
136142
return nil
137143
}
138144

145+
// defaultExtractorFunctions maps data gatherer names to functions that extract
146+
// their data from DataReadings into the appropriate fields of a Snapshot.
147+
// Each function takes a DataReading and a pointer to a Snapshot,
148+
// and populates the relevant field(s) of the Snapshot based on the DataReading's data.
149+
// Deleted resources are excluded from the snapshot because they are not needed by CyberArk.
139150
var defaultExtractorFunctions = map[string]func(*api.DataReading, *dataupload.Snapshot) error{
140151
"ark/discovery": extractClusterIDAndServerVersionFromReading,
141152
"ark/secrets": func(r *api.DataReading, s *dataupload.Snapshot) error {
@@ -184,6 +195,7 @@ var defaultExtractorFunctions = map[string]func(*api.DataReading, *dataupload.Sn
184195
// The extractorFunctions map should contain functions for each expected
185196
// DataGatherer name, which will be called with the corresponding DataReading
186197
// and the target snapshot to populate the relevant fields.
198+
// Deleted resources are excluded from the snapshot because they are not needed by CyberArk.
187199
func convertDataReadings(
188200
extractorFunctions map[string]func(*api.DataReading, *dataupload.Snapshot) error,
189201
readings []*api.DataReading,

pkg/client/client_cyberark_convertdatareadings_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,19 @@ func TestExtractResourceListFromReading(t *testing.T) {
218218
},
219219
},
220220
},
221+
// Deleted resource should be ignored
222+
{
223+
DeletedAt: api.Time{Time: time.Now()},
224+
Resource: &unstructured.Unstructured{
225+
Object: map[string]interface{}{
226+
"kind": "Namespace",
227+
"metadata": map[string]interface{}{
228+
"name": "kube-system",
229+
"uid": "uid-kube-system",
230+
},
231+
},
232+
},
233+
},
221234
},
222235
},
223236
},
@@ -270,6 +283,16 @@ func TestConvertDataReadings(t *testing.T) {
270283
},
271284
},
272285
},
286+
// Deleted secret should be ignored
287+
{
288+
DeletedAt: api.Time{Time: time.Now()},
289+
Resource: &corev1.Secret{
290+
ObjectMeta: metav1.ObjectMeta{
291+
Name: "deleted-1",
292+
Namespace: "team-1",
293+
},
294+
},
295+
},
273296
},
274297
},
275298
},

pkg/datagatherer/k8s/dynamic.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
package k8s
22

3+
// The venafi-kubernetes-agent has a requirement that **all** resources should
4+
// be uploaded, even short-lived secrets, which are created and deleted
5+
// in-between data uploads. A cache was added to the datagatherer code, to
6+
// satisfy this requirement. The cache stores all resources for 5 minutes. And
7+
// the informer event handlers (onAdd, onUpdate, onDelete) update the cache
8+
// accordingly. The onDelete handler does not remove the object from the cache,
9+
// but instead marks the object as deleted by setting the DeletedAt field on the
10+
// GatheredResource. This ensures that deleted resources are still present in
11+
// the cache for the duration of the cache expiry time.
12+
//
13+
// The cache expiry is hard coded to 5 minutes, which is longer than the
14+
// venafi-kubernetes-agent default upload interval of 1 minute. This means that
15+
// even if a resource is created and deleted in-between data gatherer runs, it
16+
// will still be present in the cache when the data gatherer runs.
17+
//
18+
// TODO(wallrj): When the agent is deployed as CyberArk disco-agent, the deleted
19+
// items are currently discarded before upload. If this remains the case, then the cache is unnecessary
20+
// and should be disabled to save memory.
21+
// If, in the future, the CyberArk Discovery and Context service does want to
22+
// see deleted items, the "deleted resource reporting mechanism" will need to be
23+
// redesigned, so that deleted items are retained for the duration of the upload
24+
// interval.
25+
//
26+
// TODO(wallrj): When the agent is deployed as CyberArk disco-agent, the upload
27+
// interval is 12 hours by default, so the 5 minute cache expiry is not
28+
// sufficient.
29+
//
30+
// TODO(wallrj): The shared informer is configured to refresh all relist all
31+
// resources every 1 minute, which will cause unnecessary load on the apiserver.
32+
// We need to look back at the Git history and understand whether this was done
33+
// for good reason or due to some misunderstanding.
34+
335
import (
436
"context"
537
"errors"
@@ -197,6 +229,8 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
197229

198230
if informerFunc, ok := kubernetesNativeResources[c.GroupVersionResource]; ok {
199231
factory := informers.NewSharedInformerFactoryWithOptions(clientset,
232+
// TODO(wallrj): This causes all resources to be relisted every 1
233+
// minute which will cause unnecessary load on the apiserver.
200234
60*time.Second,
201235
informers.WithNamespace(metav1.NamespaceAll),
202236
informers.WithTweakListOptions(func(options *metav1.ListOptions) {
@@ -207,6 +241,8 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
207241
} else {
208242
factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
209243
cl,
244+
// TODO(wallrj): This causes all resources to be relisted every 1
245+
// minute which will cause unnecessary load on the apiserver.
210246
60*time.Second,
211247
metav1.NamespaceAll,
212248
func(options *metav1.ListOptions) {

0 commit comments

Comments
 (0)