19
19
from __future__ import print_function
20
20
21
21
import os
22
+ import io
23
+ from pathlib import Path
24
+ from itertools import chain
25
+ import tarfile
22
26
import tempfile
23
27
import shutil
24
28
from oscap_docker_python .get_cve_input import getInputCVE
@@ -45,63 +49,51 @@ def __init__(self, target, is_image=False, oscap_binary='oscap'):
45
49
# init docker high level API (to deal with start/stop/run containers/image)
46
50
self .client_api = docker .from_env ()
47
51
self .is_image = is_image
48
- self .stop_at_end = False # stop the container after scan if True
49
52
self .oscap_binary = oscap_binary or 'oscap'
50
53
self .container_name = None
51
54
self .image_name = None
55
+ self .extracted_container = False
52
56
53
57
# init docker low level api (useful for deep details like container pid)
54
58
self .client = self .client_api .api
55
59
56
60
if self .is_image :
57
61
self .image_name , self .config = self ._get_image_name_and_config (target )
58
62
if self .image_name :
59
- print ("Running given image in a temporary container ..." )
63
+ print ("Creating a temporary container for the image ..." )
60
64
self .container_name = "tmp_oscap_" + str (uuid .uuid1 ())
61
65
62
66
try :
63
67
tmp_cont = self .client .create_container (
64
- self .image_name , 'bash' , name = self .container_name , tty = True )
65
- # tty=True is required in order to keep the container running
66
- self .client .start (container = tmp_cont .get ('Id' ))
68
+ self .image_name , name = self .container_name )
67
69
68
70
self .config = self .client .inspect_container (self .container_name )
69
- if int (self .config ["State" ]["Pid" ]) == 0 :
70
- sys .stderr .write ("Cannot run image {0}.\n " .format (self .image_name ))
71
- else :
72
- self .pid = int (self .config ["State" ]["Pid" ])
73
71
except Exception as e :
74
- sys .stderr .write ("Cannot run image {0}.\n " .format (self .image_name ))
72
+ sys .stderr .write ("Cannot create container for image {0}.\n " .format (self .image_name ))
75
73
raise e
74
+
75
+ self ._extract_container ()
76
76
else :
77
77
raise ValueError ("Image {0} not found.\n " .format (target ))
78
78
79
79
else :
80
80
self .container_name , self .config = \
81
81
self ._get_container_name_and_config (target )
82
+ if not self .container_name :
83
+ raise ValueError ("Container {0} not found.\n " .format (target ))
82
84
83
85
# is the container running ?
84
86
if int (self .config ["State" ]["Pid" ]) == 0 :
85
- print ("Container {0} is stopped, running it temporarily ... "
87
+ print ("Container {0} is stopped"
86
88
.format (self .container_name ))
87
89
88
- self .client_api .containers .get (self .container_name ).start ()
89
- self .container_name , self .config = \
90
- self ._get_container_name_and_config (target )
91
-
92
- if int (self .config ["State" ]["Pid" ]) == 0 :
93
- sys .stderr .write (
94
- "Cannot keep running container {0}, skip it.\n \
95
- Please start this container before scan it.\n "
96
- .format (self .container_name ))
97
- else :
98
- self .stop_at_end = True
99
-
100
- # now we are sure that the container is running, get its PID
101
- self .pid = int (self .config ["State" ]["Pid" ])
90
+ self ._extract_container ()
91
+ else :
92
+ print ("Container {0} is running, using its existing mount..."
93
+ .format (self .container_name ))
94
+ self .mountpoint = "/proc/{0}/root" .format (self .config ["State" ]["Pid" ])
102
95
103
96
if self ._check_container_mountpoint ():
104
- self .mountpoint = "/proc/{0}/root" .format (self .pid )
105
97
print ("Docker container {0} ready to be scanned."
106
98
.format (self .container_name ))
107
99
else :
@@ -113,14 +105,27 @@ def __init__(self, target, is_image=False, oscap_binary='oscap'):
113
105
114
106
def _end (self ):
115
107
if self .is_image :
116
- # stop and remove the temporary container
117
- self .client .stop (self .container_name )
108
+ # remove the temporary container
118
109
self .client .remove_container (self .container_name )
119
110
print ("Temporary container {0} cleaned" .format (self .container_name ))
120
- else :
121
- if self .stop_at_end :
122
- # just stop the container if the tool have started it.
123
- self .client .stop (self .container_name )
111
+ if self .extracted_container :
112
+ print ("Cleaning temporary extracted container..." )
113
+ shutil .rmtree (self .mountpoint )
114
+
115
+ def _extract_container (self ):
116
+ '''
117
+ Extracts the container and sets mountpoint to the extracted directory
118
+ '''
119
+ with tempfile .TemporaryFile () as tar :
120
+ for chunk in self .client .export (self .container_name ):
121
+ tar .write (chunk )
122
+ tar .seek (0 )
123
+ self .mountpoint = tempfile .mkdtemp ()
124
+ self .extracted_container = True
125
+ with tarfile .open (fileobj = tar ) as tf :
126
+ tf .extractall (path = self .mountpoint )
127
+ Path (os .path .join (self .mountpoint , '.dockerenv' )).touch ()
128
+
124
129
125
130
def _get_image_name_and_config (self , target ):
126
131
'''
@@ -159,7 +164,7 @@ def _check_container_mountpoint(self):
159
164
'''
160
165
Ensure that the container fs is well mounted and return its path
161
166
'''
162
- return os .access ("/proc/{0}/root" . format ( self .pid ) , os .R_OK )
167
+ return os .access (self .mountpoint , os .R_OK )
163
168
164
169
def scan_cve (self , scan_args ):
165
170
'''
@@ -210,7 +215,7 @@ def scan(self, scan_args):
210
215
Wrapper function forwarding oscap args for an offline scan
211
216
'''
212
217
scan_result = oscap_chroot (
213
- "/proc/{0}/root" . format ( self .pid ) ,
218
+ self .mountpoint ,
214
219
self .oscap_binary , scan_args ,
215
220
self .image_name or self .container_name ,
216
221
self .config ["Config" ].get ("Env" , []) or [] # because Env can exists but be None
0 commit comments