Skip to content

Commit 460b085

Browse files
verbosity + refactoring
1 parent cf38b92 commit 460b085

File tree

4 files changed

+132
-120
lines changed

4 files changed

+132
-120
lines changed

utils/oscap-docker.in

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
''' oscap docker command '''
2222

2323
import argparse
24-
from oscap_docker_python.oscap_docker_util import OscapAtomicScan,\
25-
OscapDockerScan, isAtomicLoaded
24+
from oscap_docker_python.oscap_docker_util import OscapAtomicScan, \
25+
OscapDockerScan, isAtomicLoaded
26+
2627
import docker
2728
import traceback
2829
import sys
@@ -44,8 +45,11 @@ if __name__ == '__main__':
4445
parser = argparse.ArgumentParser(description='oscap docker',
4546
epilog='See `man oscap` to learn \
4647
more about OSCAP-ARGUMENTS')
47-
parser.add_argument('--oscap', dest='oscap_binary', default='', help='Set the oscap binary to use')
48-
parser.add_argument('--disable-atomic', dest='noatomic', action='store_true', help="Force to use native docker API instead of atomic")
48+
parser.add_argument('--oscap', dest='oscap_binary', default='',
49+
help='Set the oscap binary to use')
50+
51+
parser.add_argument('--disable-atomic', dest='noatomic', action='store_true',
52+
help="Force to use native docker API instead of atomic")
4953
subparser = parser.add_subparsers(help="commands")
5054

5155
# Scan CVEs in image
@@ -91,6 +95,7 @@ if __name__ == '__main__':
9195

9296
try:
9397
if isAtomicLoaded and not args.noatomic:
98+
print("Using Atomic API")
9499
OS = OscapAtomicScan(oscap_binary=args.oscap_binary)
95100
if args.action == "scan":
96101
rc = OscapAtomicScan.scan(OS, args.scan_target, leftover_args)
@@ -102,7 +107,7 @@ if __name__ == '__main__':
102107

103108
else: # without atomic
104109
if args.noatomic:
105-
print("Running oscap-docker with native docker api instead of atomic ...")
110+
print("Using native Docker API")
106111

107112
ODS = OscapDockerScan(args.scan_target, args.is_image, args.oscap_binary)
108113
if args.action == "scan":
@@ -113,16 +118,13 @@ if __name__ == '__main__':
113118
parser.print_help()
114119
sys.exit(2)
115120

116-
except ValueError as e:
117-
raise e
118-
sys.exit(255)
119-
except RuntimeError as e:
121+
except (ValueError, RuntimeError) as e:
120122
raise e
121123
sys.exit(255)
122124
except Exception as exc:
123125
traceback.print_exc(file=sys.stdout)
124-
sys.stderr.write("!!! WARNING !!! This software have crashed, so you should "
125-
"check that no temporary container is still running\n")
126+
sys.stderr.write("!!! WARNING !!! This software has crashed, so you should "
127+
"check that no temporary container is still running\n")
126128
sys.exit(255)
127129

128130
sys.exit(rc)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright (C) 2015 Brent Baude <[email protected]>
2+
# Copyright (C) 2019 Dominique Blaze <[email protected]>
3+
#
4+
# This library is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License as published by the Free Software Foundation; either
7+
# version 2 of the License, or (at your option) any later version.
8+
#
9+
# This library is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
# Lesser General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Lesser General Public
15+
# License along with this library; if not, write to the
16+
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17+
# Boston, MA 02111-1307, USA.
18+
19+
import subprocess
20+
import platform
21+
import os
22+
import collections
23+
24+
25+
class OscapError(Exception):
26+
''' oscap Error'''
27+
pass
28+
29+
OscapResult = collections.namedtuple("OscapResult", ("returncode", "stdout", "stderr"))
30+
31+
32+
def oscap_chroot(chroot_path, oscap_binary, oscap_args, target_name, local_env=[]):
33+
'''
34+
Wrapper running oscap_chroot on an OscapDockerScan OscapAtomicScan object
35+
'''
36+
os.environ["OSCAP_PROBE_ARCHITECTURE"] = platform.processor()
37+
os.environ["OSCAP_PROBE_ROOT"] = os.path.join(chroot_path)
38+
os.environ["OSCAP_PROBE_OS_NAME"] = platform.system()
39+
os.environ["OSCAP_PROBE_OS_VERSION"] = platform.release()
40+
41+
os.environ["OSCAP_EVALUATION_TARGET"] = target_name
42+
43+
for var in local_env:
44+
vname, val = var.split("=", 1)
45+
os.environ["OSCAP_OFFLINE_" + vname] = val
46+
47+
cmd = [oscap_binary] + [x for x in oscap_args]
48+
49+
oscap_process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
50+
stderr=subprocess.PIPE)
51+
oscap_stdout, oscap_stderr = oscap_process.communicate()
52+
return OscapResult(oscap_process.returncode,
53+
oscap_stdout.decode("utf-8"),
54+
oscap_stderr.decode("utf-8"))
55+
56+
# TODO replace by _get_cpe (in order to indentify any containerized system)
57+
58+
59+
def get_dist(mountpoint, oscap_binary, local_env):
60+
CPE_RHEL = 'oval:org.open-scap.cpe.rhel:def:'
61+
DISTS = ["8", "7", "6", "5"]
62+
63+
'''
64+
Test the chroot and determine what RHEL dist it is; returns
65+
an integer representing the dist
66+
'''
67+
68+
cpe_dict = '/usr/share/openscap/cpe/openscap-cpe-oval.xml'
69+
if not os.path.exists(cpe_dict):
70+
# sometime it's installed into /usr/local/share instead of /usr/local
71+
cpe_dict = '/usr/local/share/openscap/cpe/openscap-cpe-oval.xml'
72+
if not os.path.exists(cpe_dict):
73+
raise OscapError()
74+
75+
for dist in DISTS:
76+
result = oscap_chroot(
77+
mountpoint, oscap_binary,
78+
("oval", "eval", "--id", CPE_RHEL + dist, cpe_dict,
79+
mountpoint, "2>&1", ">", "/dev/null"),
80+
'*',
81+
local_env
82+
)
83+
84+
if "{0}{1}: true".format(CPE_RHEL, dist) in result.stdout:
85+
print("This system seems based on RHEL{0}.".format(dist))
86+
return dist

utils/oscap_docker_python/oscap_docker_util.py

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import docker
3131
import collections
3232
from oscap_docker_python.oscap_docker_util_noatomic import OscapDockerScan
33+
from oscap_docker_python.oscap_docker_common import oscap_chroot, get_dist, \
34+
OscapResult, OscapError
3335

3436
atomic_loaded = False
3537

@@ -86,14 +88,6 @@ def isAtomicLoaded():
8688
return atomic_loaded
8789

8890

89-
class OscapError(Exception):
90-
''' oscap Error'''
91-
pass
92-
93-
94-
OscapResult = collections.namedtuple("OscapResult", ("returncode", "stdout", "stderr"))
95-
96-
9791
class OscapHelpers(object):
9892
''' oscap class full of helpers for scanning '''
9993
CPE = 'oval:org.open-scap.cpe.rhel:def:'
@@ -120,24 +114,6 @@ def _rm_tmp_dir(tmp_dir):
120114
'''
121115
shutil.rmtree(tmp_dir)
122116

123-
def _get_dist(self, chroot, target):
124-
'''
125-
Test the chroot and determine what RHEL dist it is; returns
126-
an integer representing the dist
127-
'''
128-
cpe_dict = '/usr/share/openscap/cpe/openscap-cpe-oval.xml'
129-
if not os.path.exists(cpe_dict):
130-
cpe_dict = '/usr/local/share/openscap/cpe/openscap-cpe-oval.xml'
131-
if not os.path.exists(cpe_dict):
132-
raise OscapError()
133-
134-
for dist in self.DISTS:
135-
result = self.oscap_chroot(chroot, target, 'oval', 'eval',
136-
'--id', self.CPE + dist, cpe_dict,
137-
'2>&1', '>', '/dev/null')
138-
if "{0}{1}: true".format(self.CPE, dist) in result.stdout:
139-
return dist
140-
141117
def _get_target_name_and_config(self, target):
142118
'''
143119
Determines if target is image or container. For images returns full
@@ -166,40 +142,30 @@ def _get_target_name_and_config(self, target):
166142
except docker.errors.NotFound:
167143
return "unknown", {}
168144

169-
def oscap_chroot(self, chroot_path, target, *oscap_args):
170-
'''
171-
Wrapper function for executing oscap in a subprocess
172-
'''
173-
os.environ["OSCAP_PROBE_ARCHITECTURE"] = platform.processor()
174-
os.environ["OSCAP_PROBE_ROOT"] = os.path.join(chroot_path)
175-
os.environ["OSCAP_PROBE_OS_NAME"] = platform.system()
176-
os.environ["OSCAP_PROBE_OS_VERSION"] = platform.release()
177-
name, conf = self._get_target_name_and_config(target)
178-
os.environ["OSCAP_EVALUATION_TARGET"] = name
179-
for var in conf.get("Env", []):
180-
vname, val = var.split("=", 1)
181-
os.environ["OSCAP_OFFLINE_" + vname] = val
182-
cmd = [self.oscap_binary] + [x for x in oscap_args]
183-
oscap_process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
184-
oscap_stdout, oscap_stderr = oscap_process.communicate()
185-
return OscapResult(oscap_process.returncode,
186-
oscap_stdout.decode("utf-8"), oscap_stderr.decode("utf-8"))
187-
188145
def _scan_cve(self, chroot, target, dist, scan_args):
189146
'''
190147
Scan a chroot for cves
191148
'''
192149
cve_input = getInputCVE.dist_cve_name.format(dist)
193-
tmp_tuple = ('oval', 'eval') + tuple(scan_args) + \
194-
(os.path.join(self.cve_input_dir, cve_input),)
195-
return self.oscap_chroot(chroot, target, *tmp_tuple)
150+
151+
args = ("oval", "eval")
152+
for a in scan_args:
153+
args += (a,)
154+
args += (os.path.join(self.cve_input_dir, cve_input),)
155+
156+
name, conf = self._get_target_name_and_config(target)
157+
158+
return oscap_chroot(chroot, self.oscap_binary, args, name,
159+
conf.get("Env", []) or [])
196160

197161
def _scan(self, chroot, target, scan_args):
198162
'''
199163
Scan a container or image
200164
'''
201-
tmp_tuple = tuple(scan_args)
202-
return self.oscap_chroot(chroot, target, *tmp_tuple)
165+
166+
name, conf = self._get_target_name_and_config(target)
167+
return oscap_chroot(chroot, target, scan_args, name,
168+
conf.get("Env", []) or [])
203169

204170
def resolve_image(self, image):
205171
'''
@@ -287,7 +253,8 @@ def scan_cve(self, image, scan_args):
287253
chroot = self._find_chroot_path(_tmp_mnt_dir)
288254

289255
# Figure out which RHEL dist is in the chroot
290-
dist = self.helper._get_dist(chroot, image)
256+
name, conf = self.helper._get_target_name_and_config(image)
257+
dist = get_dist(chroot, self.helper.oscap_binary, conf.get("Env", []) or [])
291258

292259
if dist is None:
293260
sys.stderr.write("{0} is not based on RHEL\n".format(image))

utils/oscap_docker_python/oscap_docker_util_noatomic.py

Lines changed: 15 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020

2121
import os
2222
import tempfile
23-
import subprocess
24-
import platform
2523
import shutil
2624
from oscap_docker_python.get_cve_input import getInputCVE
2725
import sys
2826
import docker
2927
import uuid
3028
import collections
29+
from oscap_docker_python.oscap_docker_common import oscap_chroot, get_dist, \
30+
OscapResult, OscapError
3131

3232

3333
class OscapError(Exception):
@@ -39,8 +39,6 @@ class OscapError(Exception):
3939

4040

4141
class OscapDockerScan(object):
42-
CPE_RHEL = 'oval:org.open-scap.cpe.rhel:def:'
43-
DISTS = ["8", "7", "6", "5"]
4442

4543
def __init__(self, target, is_image=False, oscap_binary='oscap'):
4644

@@ -107,7 +105,7 @@ def __init__(self, target, is_image=False, oscap_binary='oscap'):
107105

108106
if self._check_container_mountpoint():
109107
self.mountpoint = "/proc/{0}/root".format(self.pid)
110-
print("Docker container {0} ready to be scanned !"
108+
print("Docker container {0} ready to be scanned."
111109
.format(self.container_name))
112110
else:
113111
self._end()
@@ -166,29 +164,6 @@ def _check_container_mountpoint(self):
166164
'''
167165
return os.access("/proc/{0}/root".format(self.pid), os.R_OK)
168166

169-
# TODO replace by _get_cpe (in order to indentify any containerized system)
170-
def _get_dist(self):
171-
'''
172-
Test the chroot and determine what RHEL dist it is; returns
173-
an integer representing the dist
174-
'''
175-
cpe_dict = '/usr/share/openscap/cpe/openscap-cpe-oval.xml'
176-
if not os.path.exists(cpe_dict):
177-
cpe_dict = '/usr/local/share/openscap/cpe/openscap-cpe-oval.xml'
178-
if not os.path.exists(cpe_dict):
179-
raise OscapError()
180-
181-
for dist in self.DISTS:
182-
result = self.oscap_chroot(
183-
self.mountpoint, self.oscap_binary,
184-
("oval", "eval", "--id", self.CPE_RHEL + dist, cpe_dict,
185-
self.mountpoint, "2>&1", ">", "/dev/null")
186-
)
187-
188-
if "{0}{1}: true".format(self.CPE_RHEL, dist) in result.stdout:
189-
print("This system seems bases on RHEL{0}.".format(dist))
190-
return dist
191-
192167
def scan_cve(self, scan_args):
193168
'''
194169
Wrapper function for scanning cve of a mounted container
@@ -197,7 +172,8 @@ def scan_cve(self, scan_args):
197172
tmp_dir = tempfile.mkdtemp()
198173

199174
# Figure out which RHEL dist is in the chroot
200-
dist = self._get_dist()
175+
dist = get_dist(self.mountpoint, self.oscap_binary,
176+
self.config["Config"].get("Env", []) or [])
201177

202178
if dist is None:
203179
sys.stderr.write("{0} is not based on RHEL\n"
@@ -215,8 +191,11 @@ def scan_cve(self, scan_args):
215191
args += (a,)
216192
args += (cve_file,)
217193

218-
scan_result = self.oscap_chroot(
219-
self.mountpoint, self.oscap_binary, args)
194+
scan_result = oscap_chroot(
195+
self.mountpoint, self.oscap_binary, args,
196+
self.image_name or self.container_name,
197+
self.config["Config"].get("Env", []) or [] # because Env can exists but be None
198+
)
220199

221200
print(scan_result.stdout)
222201
print(scan_result.stderr, file=sys.stderr)
@@ -233,38 +212,16 @@ def scan(self, scan_args):
233212
'''
234213
Wrapper function forwarding oscap args for an offline scan
235214
'''
236-
scan_result = self.oscap_chroot(
215+
scan_result = oscap_chroot(
237216
"/proc/{0}/root".format(self.pid),
238-
self.oscap_binary, scan_args
217+
self.oscap_binary, scan_args,
218+
self.image_name or self.container_name,
219+
self.config["Config"].get("Env", []) or [] # because Env can exists but be None
239220
)
221+
240222
print(scan_result.stdout)
241223
print(scan_result.stderr, file=sys.stderr)
242224

243225
self._end()
244226

245227
return scan_result.returncode
246-
247-
def oscap_chroot(self, chroot_path, oscap_binary, oscap_args):
248-
'''
249-
Wrapper running oscap_chroot on an OscapDockerScan object
250-
'''
251-
os.environ["OSCAP_PROBE_ARCHITECTURE"] = platform.processor()
252-
os.environ["OSCAP_PROBE_ROOT"] = os.path.join(chroot_path)
253-
os.environ["OSCAP_PROBE_OS_NAME"] = platform.system()
254-
os.environ["OSCAP_PROBE_OS_VERSION"] = platform.release()
255-
256-
os.environ["OSCAP_EVALUATION_TARGET"] = self.container_name
257-
258-
if self.config["Config"].get("Env"): # Env can be exists but be None
259-
for var in self.config["Config"].get("Env", []):
260-
vname, val = var.split("=", 1)
261-
os.environ["OSCAP_OFFLINE_" + vname] = val
262-
263-
cmd = [oscap_binary] + [x for x in oscap_args]
264-
265-
oscap_process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
266-
stderr=subprocess.PIPE)
267-
oscap_stdout, oscap_stderr = oscap_process.communicate()
268-
return OscapResult(oscap_process.returncode,
269-
oscap_stdout.decode("utf-8"),
270-
oscap_stderr.decode("utf-8"))

0 commit comments

Comments
 (0)