32
32
33
33
CE_BINARY = '/opt/container-explorer/bin/ce'
34
34
CE_SUPPORT_FILE = '/opt/container-explorer/etc/supportcontainer.yaml'
35
+ POD_NAME_LABEL = 'io.kubernetes.pod.name'
35
36
36
37
37
38
class ContainerdEnumerationTask (TurbiniaTask ):
38
39
"""Enumerate containerd containers on Linux."""
39
40
40
41
REQUIRED_STATES = [state .ATTACHED , state .MOUNTED ]
41
42
43
+ TASK_CONFIG = {
44
+ # These filters will all match on partial matches, e.g. an image filter of
45
+ # ['gke.gcr.io/'] will filter out image `gke.gcr.io/event-exporter`.
46
+ #
47
+ # Which k8 namespaces to filter out by default
48
+ 'filter_namespaces' : ['kube-system' ],
49
+ 'filter_pod_names' : ['sidecar' , 'k8s-sidecar' , 'konnectivity-agent' ],
50
+ # Taken from
51
+ # https://github.com/google/container-explorer/blob/main/supportcontainer.yaml
52
+ 'filter_images' : [
53
+ 'gcr.io/gke-release-staging/cluster-proportional-autoscaler-amd64' ,
54
+ 'gcr.io/k8s-ingress-image-push/ingress-gce-404-server-with-metrics' ,
55
+ 'gke.gcr.io/ingress-gce-404-server-with-metrics' ,
56
+ 'gke.gcr.io/cluster-proportional-autoscaler' ,
57
+ 'gke.gcr.io/csi-node-driver-registrar' ,
58
+ 'gke.gcr.io/event-exporter' ,
59
+ 'gke.gcr.io/fluent-bit' ,
60
+ 'gke.gcr.io/fluent-bit-gke-exporter' ,
61
+ 'gke.gcr.io/gcp-compute-persistent-disk-csi-driver' ,
62
+ 'gke.gcr.io/gke-metrics-agent' ,
63
+ 'gke.gcr.io/k8s-dns-dnsmasq-nanny' ,
64
+ 'gke.gcr.io/k8s-dns-kube-dns' ,
65
+ 'gke.gcr.io/k8s-dns-sidecar' ,
66
+ 'gke.gcr.io/kube-proxy-amd64' ,
67
+ 'gke.gcr.io/prometheus-to-sd' ,
68
+ 'gke.gcr.io/proxy-agent' ,
69
+ 'k8s.gcr.io/metrics-server/metrics-server' ,
70
+ 'gke.gcr.io/metrics-server' ,
71
+ 'k8s.gcr.io/pause' ,
72
+ 'gke.gcr.io/pause' ,
73
+ 'gcr.io/gke-release-staging/addon-resizer' ,
74
+ 'gcr.io/gke-release-staging/cpvpa-amd64' ,
75
+ 'gcr.io/google-containers/pause-amd64' ,
76
+ 'gke.gcr.io/addon-resizer' ,
77
+ 'gke.gcr.io/cpvpa-amd64' ,
78
+ 'k8s.gcr.io/kube-proxy-amd64' ,
79
+ 'k8s.gcr.io/prometheus-to-sd' ,
80
+ ],
81
+ }
82
+
42
83
def list_containers (self , evidence , _ , detailed_output = False ):
43
84
"""List containerd containers in the evidence.
44
85
@@ -95,8 +136,8 @@ def _list_containers_result(self, containers, detailed_output):
95
136
return containers
96
137
97
138
basic_fields = [
98
- 'Namespace' , 'Image' , 'ContainerType' , 'ID' , 'Hostname' , 'CreatedAt'
99
- 'Labels'
139
+ 'Name' , ' Namespace' , 'Image' , 'ContainerType' , 'ID' , 'Hostname' ,
140
+ 'CreatedAt' , ' Labels'
100
141
]
101
142
basic_containers = []
102
143
@@ -123,10 +164,16 @@ def run(self, evidence, result):
123
164
summary = ''
124
165
success = False
125
166
report_data = []
167
+ filter_namespaces = self .task_config .get ('filter_namespaces' )
168
+ filter_pod_names = self .task_config .get ('filter_pod_names' )
169
+ filter_images = self .task_config .get ('filter_images' )
170
+ filtered_container_list = []
126
171
127
172
image_path = evidence .mount_path
128
173
if not image_path :
129
- summary = f'Evidence { evidence .name } :{ evidence .source_path } is not mounted'
174
+ summary = (
175
+ f'Evidence { evidence .name } :{ evidence .source_path } is not '
176
+ 'mounted' )
130
177
result .close (self , success = False , status = summary )
131
178
return result
132
179
@@ -142,20 +189,62 @@ def run(self, evidence, result):
142
189
f'Found { len (container_ids )} containers: { ", " .join (container_ids )} ' )
143
190
144
191
# 2. Add containers as evidences
192
+ new_evidence = []
145
193
for container in containers :
146
- namespace = container .get ('Namespace' )
147
- container_id = container .get ('ID' )
194
+ namespace = container .get ('Namespace' , 'UnknownNamespace' )
195
+ container_id = container .get ('ID' , 'UnknownContainerID' )
196
+ if container .get ('Labels' ):
197
+ pod_name = container .get ('Labels' ).get (
198
+ POD_NAME_LABEL , 'UnknownPodName' )
199
+ else :
200
+ pod_name = 'UnknownPodName'
148
201
container_type = container .get ('ContainerType' ) or None
202
+ image = container .get ('Image' )
203
+ if image :
204
+ image_short = image .split ('@' )[0 ]
205
+ image_short = image_short .split (':' )[0 ]
206
+ else :
207
+ image_short = 'UnknownImageName'
149
208
150
209
if not namespace or not container_id :
151
- result .log (
152
- f'Value is empty. namespace={ namespace } , container_id={ container_id } '
153
- )
154
- report_data .append (
210
+ message = (
155
211
f'Skipping container with empty value namespace ({ namespace } )'
156
212
f' or container_id ({ container_id } )' )
213
+ result .log (message )
214
+ report_data .append (message )
157
215
continue
158
216
217
+ # Filter out configured namespaces/containers/images. Even though we
218
+ # could let container explorer filter these before we get them we want
219
+ # to do it here so that we can report on what was available and filtered
220
+ # out to give the analyst the option to reprocess these containers.
221
+ if filter_namespaces :
222
+ if namespace in filter_namespaces :
223
+ message = (
224
+ f'Filtering out container { container_id } because namespace '
225
+ f'matches filter.' )
226
+ result .log (message )
227
+ report_data .append (message )
228
+ filtered_container_list .append (container_id )
229
+ continue
230
+ if filter_images :
231
+ if image_short in filter_images :
232
+ message = (
233
+ f'Filtering out image { image } because image matches filter' )
234
+ result .log (message )
235
+ report_data .append (message )
236
+ filtered_container_list .append (container_id )
237
+ continue
238
+ if filter_pod_names :
239
+ if pod_name in filter_pod_names :
240
+ message = (
241
+ f'Filtering out container { container_id } because container '
242
+ f'name matches filter' )
243
+ result .log (message )
244
+ report_data .append (message )
245
+ filtered_container_list .append (container_id )
246
+ continue
247
+
159
248
# We want to process docker managed container using Docker-Explorer
160
249
if container_type and container_type .lower () == 'docker' :
161
250
result .log (
@@ -165,12 +254,29 @@ def run(self, evidence, result):
165
254
continue
166
255
167
256
container_evidence = ContainerdContainer (
168
- namespace = namespace , container_id = container_id )
257
+ namespace = namespace , container_id = container_id ,
258
+ image_name = image_short , pod_name = pod_name )
259
+ new_evidence .append (container_evidence .name )
169
260
170
261
result .add_evidence (container_evidence , evidence .config )
262
+ result .log (
263
+ f'Adding container evidence { container_evidence .name } '
264
+ f'type { container_type } ' )
265
+
171
266
summary = (
172
- f'Found { len (container_ids )} containers: { ", " .join (container_ids )} ' )
267
+ f'Found { len (container_ids )} containers, added { len (new_evidence )} '
268
+ f'(filtered out { len (filtered_container_list )} )' )
173
269
success = True
270
+ if filtered_container_list :
271
+ report_data .append (
272
+ f'Filtered out { len (filtered_container_list )} containers: '
273
+ f'{ ", " .join (filtered_container_list )} ' )
274
+ report_data .append (
275
+ f'Container filter lists: Namespaces: { filter_namespaces } , Images: { filter_images } , '
276
+ f'Pod Names: { filter_pod_names } ' )
277
+ report_data .append (
278
+ 'To process filtered containers, adjust the ContainerEnumeration '
279
+ 'Task config filter* parameters with a recipe' )
174
280
except TurbiniaException as e :
175
281
summary = f'Error enumerating containerd containers: { e } '
176
282
report_data .append (summary )
0 commit comments