Changes that are made to imported benchmarks (currently just
selections) are now stored in config files in the same directory as the
benchmark. Also implemented an initial export command that will export
an imported benchmark and associated oval to a zipfile. Also added
'close' statements for all files that are opened.
Has a couple small fixes not in the last patch I sent.
Fixes bugs #7147 and #7148
---
src/bin/secstate | 12 +++++
src/secstate/main.py | 119 +++++++++++++++++++++++++++++++++----------------
src/secstate/util.py | 25 ++++++++++-
3 files changed, 116 insertions(+), 40 deletions(-)
diff --git a/src/bin/secstate b/src/bin/secstate
index 0c28ecc..0dfdbdb 100644
--- a/src/bin/secstate
+++ b/src/bin/secstate
@@ -70,6 +70,9 @@ def main():
if subcommand == 'import':
return import_content(sys.argv[arg_num:])
+ elif subcommand == 'export':
+ return export(sys.argv[arg_num:])
+
elif subcommand == 'remove':
return remove_content(sys.argv[arg_num:])
@@ -118,6 +121,15 @@ def import_content(arguments):
oscap.oval_definition_model_free(def_model)
oscap.xccdf_benchmark_free(benchmark)
+def export(arguments):
+ parser = OptionParser(usage="secstate export [options] <benchmark> <file>")
+ parser.add_option('-o', '--original', action='store_true', dest='original', default=False,
+ help="Exports the specified XCCDF content to the specified file")
+ (options, args) = parser.parse_args(arguments)
+ for arg in args[1:]:
+ if not sec_instance.export(args[0], arg, options.original):
+ return -1
+
def remove_content(arguments):
parser = OptionParser(usage="secstate remove [options] <content>")
(options, args) = parser.parse_args(arguments)
diff --git a/src/secstate/main.py b/src/secstate/main.py
index 5543203..1b22d67 100644
--- a/src/secstate/main.py
+++ b/src/secstate/main.py
@@ -32,6 +32,7 @@ import tempfile
import re
import subprocess
import time
+import mimetypes
import openscap as oscap
from secstate.util import *
@@ -46,11 +47,13 @@ class Secstate:
def setConfigFile(self, conf):
config = ConfigParser.ConfigParser()
try:
- config.readfp(open(conf))
+ fp = open(conf)
+ config.readfp(fp)
except IOError,e:
self.log.error("Could not open config file: %(error)s" % {'error':e})
return None
self.config = config
+ fp.close()
def isDebugging(self):
return self.config.getint('logging', 'debugging') > 0
@@ -84,8 +87,9 @@ class Secstate:
db = open(db_file)
database = pickle.load(db)
except IOError,e:
- self.log.error("Error loading database: %(file)s" % {'file':db_file})
- return None
+ self.log.error("Error loading database: %(file)s" % {'file':db_file})
+ return None
+ db.close()
return database
else:
@@ -160,6 +164,7 @@ class Secstate:
return (None, None)
pickle.dump(self.database, db_file)
+ db_file.close()
return (benchmark, def_model)
def import_zipped_content(self, zip, type, store_path, puppet):
@@ -191,6 +196,8 @@ class Secstate:
for member in tar_file.getmembers():
tar_file.extract(member, extract_path)
+ tar_file.close()
+
elif type[0] == "application/zip":
if sys.version_info < (2, 6):
self.log.error("Zip file support requires Python >= 2.6")
@@ -199,6 +206,8 @@ class Secstate:
zip_files = zipfile.ZipFile(zip, 'r')
zip_files.extractall(extract_path)
+ zip_files.close()
+
else:
self.log.error("Unsupported file type: %(content)s" % {'content':zip})
return (None, None)
@@ -224,13 +233,19 @@ class Secstate:
return (benchmark, def_model)
- def import_content(self, content, store_path=None, oval=False, xccdf=False, cpe=False, puppet=False):
+ def import_content(self, content, store_path=None, oval=False, xccdf=False, cpe=False, puppet=False, changes=True):
"""
Function: Validates XCCDF/OVAL content and optionally saves it to the data store
Input: File containing content
Output: Returns a tuple containing a validated benchmar and definition model
"""
- import mimetypes
+ if self.database.has_key(content):
+ (benchmark, oval) = self.import_benchmark(os.path.join(self.benchmark_dir, content, self.database[content]), oval_path=os.path.join(self.benchmark_dir, content))
+ if changes and (benchmark != None):
+ benchmark = apply_changes(benchmark, os.path.join(self.benchmark_dir, content, str(content + ".cfg")))
+
+ return (benchmark, oval)
+
file_type = mimetypes.guess_type(content)
if file_type[0] == "text/xml":
if not (oval or xccdf):
@@ -256,6 +271,34 @@ class Secstate:
else:
return self.import_zipped_content(content, file_type, store_path, puppet)
+ def export(self, benchmark_id, new_file, original=False):
+ if not self.database.has_key(benchmark_id):
+ self.log.error("No benchmark '%(id)s' has been imported" % {'id':benchmark_id})
+ return False
+
+ benchmark_file = None
+ archive = zipfile.ZipFile(new_file, 'w')
+ if original:
+ benchmark_file = os.path.join(self.benchmark_dir, benchmark_id, self.database[benchmark_id])
+ else:
+ benchmark_file = tempfile.mktemp()
+ (benchmark, oval) = self.import_content(benchmark_id)
+ if oscap.xccdf_benchmark_export(benchmark, benchmark_file) == None:
+ self.log.error("Error exporting benchmark to %(file)s" % {'file':new_file})
+ return False
+ oscap.xccdf_benchmark_free(benchmark)
+ oscap.oval_definition_model_free(oval)
+
+ archive.write(benchmark_file, self.database[benchmark_id])
+ bench_dir = os.path.join(self.benchmark_dir, benchmark_id)
+ for content in os.listdir(bench_dir):
+ file_type = mimetypes.guess_type(os.path.join(bench_dir, content))
+ if (file_type[0] == "text/xml") and (not is_benchmark(os.path.join(bench_dir, content))):
+ archive.write(os.path.join(bench_dir, content), content)
+
+ archive.close()
+ return True
+
def remove_content(self, benchmark_id):
try:
db_file = open(self.config.get('secstate', 'benchmark_database'), 'w')
@@ -273,6 +316,7 @@ class Secstate:
self.database.clear()
pickle.dump(self.database, db_file)
+ db_file.close()
return True
if self.database.has_key(benchmark_id):
@@ -296,6 +340,21 @@ class Secstate:
Input: Benchmark id, id of rule or group, boolean value to set the items selected status
Output: Succes or failure
"""
+ bench_cfg = ConfigParser.ConfigParser()
+ bench_cfg.optionxform = str
+ conf_path = os.path.join(self.benchmark_dir, benchmark_id, str(benchmark_id + ".cfg"))
+ if os.path.isfile(conf_path):
+ try:
+ fp = open(conf_path)
+ bench_cfg.readfp(fp)
+ except IOError, e:
+ self.log.error("Could not open config file: %(error)s" % {'error':e})
+ return False
+ fp.close()
+
+ if not bench_cfg.has_section('selections'):
+ bench_cfg.add_section('selections')
+
if not self.database.has_key(benchmark_id):
self.log.error("No benchmark %(id)s in datastore" % {'id':benchmark_id})
return False
@@ -308,14 +367,10 @@ class Secstate:
return False
if item_id == 'all':
- for item in xccdf_get_items(benchmark, oscap.XCCDF_GROUP):
- oscap.xccdf_group_set_selected(item, selected)
- self.log.debug("Setting %(id)s to %(val)s" % {'id':oscap.xccdf_group_get_id(item),
- 'val':selected})
-
- for item in xccdf_get_items(benchmark, oscap.XCCDF_RULE):
- oscap.xccdf_rule_set_selected(item, selected)
- self.log.debug("Setting %(id)s to %(val)s" % {'id':oscap.xccdf_rule_get_id(item),
+
+ for item in xccdf_get_items(benchmark, oscap.XCCDF_ITEM):
+ bench_cfg.set('selections', oscap.xccdf_item_get_id(item), selected)
+ self.log.debug("Setting %(id)s to %(val)s" % {'id':oscap.xccdf_item_get_id(item),
'val':selected})
else:
@@ -325,26 +380,20 @@ class Secstate:
'item_id':item_id})
return False
- oscap.xccdf_item_set_selected(item, selected)
+ bench_cfg.set('selections', item_id, selected)
+
self.log.debug("Setting %(id)s to %(val)s" % {'id':item_id,
'val':selected})
if oscap.xccdf_item_get_type(item) == oscap.XCCDF_GROUP:
- root_group = oscap.xccdf_item_to_group(item)
- for group in xccdf_get_items(benchmark, oscap.XCCDF_GROUP, oscap.xccdf_group_get_content(root_group)):
- oscap.xccdf_group_set_selected(group, selected)
- self.log.debug("Setting %(id)s to %(val)s" % {'id':oscap.xccdf_group_get_id(group),
+ for sub in xccdf_get_items(benchmark, oscap.XCCDF_ITEM, oscap.xccdf_item_get_content(item)):
+ bench_cfg.set('selections', oscap.xccdf_item_get_id(sub), selected)
+ self.log.debug("Setting %(id)s to %(val)s" % {'id':oscap.xccdf_item_get_id(sub),
'val':selected})
- for rule in xccdf_get_items(benchmark, oscap.XCCDF_RULE, oscap.xccdf_group_get_content(root_group)):
- oscap.xccdf_rule_set_selected(rule, selected)
- self.log.debug("Setting %(id)s to %(val)s" % {'id':oscap.xccdf_group_get_id(group),
- 'val':selected})
-
- if not oscap.xccdf_benchmark_export(benchmark, benchmark_file):
- self.log.error("Error writing changes to file: %(file)s" % {'file':benchmark_file})
- return False
-
+ fp = open(conf_path, 'w')
+ bench_cfg.write(fp)
+ fp.close()
oscap.xccdf_benchmark_free(benchmark)
return True
@@ -397,14 +446,7 @@ class Secstate:
else:
for arg in args:
- benchmark_path = ""
- if self.database.has_key(arg):
- benchmark_path = os.path.join(self.benchmark_dir, arg, self.database[arg])
-
- elif os.path.isfile(arg):
- benchmark_path = arg
-
- (benchmark, def_model) = self.import_content(benchmark_path)
+ (benchmark, def_model) = self.import_content(arg)
if (benchmark == None) or (def_model == None):
self.log.error("Error importing benchmark: %(bench)s" % {'bench':arg})
return False
@@ -471,7 +513,7 @@ class Secstate:
def show(self, item_id, verbose=False):
for key in self.database:
- (benchmark, def_model) = self.import_content(os.path.join(self.benchmark_dir, key, self.database[key]))
+ (benchmark, def_model) = self.import_content(key)
if (benchmark == None) or (def_model == None):
self.log.error("Error importing content: %(file)s" % {'flie':key})
return None
@@ -573,8 +615,7 @@ class Secstate:
def list_content(self, arg=None, recurse=False, show_all=False):
ret = False
for key in self.database:
-
- (benchmark, def_model) = self.import_content(os.path.join(self.benchmark_dir, key, self.database[key]))
+ (benchmark, def_model) = self.import_content(key)
if benchmark == None:
self.log.error("Error loading benchmark: %(id)s" % {'id':key})
return False
@@ -619,7 +660,7 @@ class Secstate:
passing_ids = self.get_passed_result_ids(xccdf_results)
template = '#!/bin/sh\ncat <<"END"\n%s\nEND\nexit 0\n'
if self.database.has_key(bench_id):
- (benchmark, tmp_model) = self.import_content(os.path.join(self.benchmark_dir, bench_id, self.database[bench_id]))
+ (benchmark, tmp_model) = self.import_content(bench_id)
if not benchmark:
self.log.error("Benchmark was None")
return False
diff --git a/src/secstate/util.py b/src/secstate/util.py
index 6856212..1da13bd 100644
--- a/src/secstate/util.py
+++ b/src/secstate/util.py
@@ -19,10 +19,12 @@
# File: util.py
# This file is the core implementation of the secstate tool.
+import os
import sys
import xml.dom.minidom
import time
import re
+import ConfigParser
import openscap as oscap
@@ -387,7 +389,11 @@ def xccdf_get_items(template, type, items=None):
result.extend(xccdf_get_items(template, type, item))
if type == oscap.XCCDF_ITEM:
- result.extend(xccdf_get_items(template, type, item))
+ if (item_type == oscap.XCCDF_GROUP) or (item_type == oscap.XCCDF_BENCHMARK):
+ result.append(item)
+ result.extend(xccdf_get_items(template, type, oscap.xccdf_item_get_content(item)))
+ else:
+ result.append(item)
return result
@@ -415,6 +421,23 @@ def xccdf_rule_get_defs(rule):
return defs
+def apply_changes(benchmark, conf):
+ config = ConfigParser.ConfigParser()
+ if os.path.isfile(conf):
+ try:
+ fp = open(conf)
+ config.readfp(fp)
+ except IOError,e:
+ sys.stderr.write("Error opening config file: %(err)s\n" % {'err':e})
+ return None
+ fp.close()
+
+ for id in config.options("selections"):
+ item = oscap.xccdf_benchmark_get_item(benchmark, id)
+ oscap.xccdf_item_set_selected(item, config.getboolean('selections', id))
+
+ return benchmark
+
def xccdf_get_fixes(benchmark, ignore_ids=[]):
"""
Function: Get all fixes for rules in the XCCDF document
--
1.7.0.1