Реализация инструментов для создания контента OVAL® на Python

в 10:52, , рубрики: OODB, OVAL, python, SCAP, Блог компании Positive Technologies, информационная безопасность, метки: , , ,

Приветствую, коллеги! image
В процессе исследования языка OVAL и концепции SCAP-сканера я столкнулся с довольно серьезной проблемой, а именно с отсутствием удобных инструментов для создания контента на языке OVAL. Нет, я не утверждаю, что нет совсем ничего. Есть небольшой набор утилит, представленных на официальном сайте. Большая часть из них платная, остальные же представляют собой не очень удобные решения, больше всего похожие на XML-Notepad. В итоге я решил создать небольшой необходимый мне для работы инструмент самостоятельно, используя в качестве языка Python.

Такой выбор обусловлен хорошей репутацией языка Python как «rapid development language» и наличием богатой палитры сторонних библиотек. Вооружившись документацией по языку, я попытался воссоздать задумки MITRE в реальности. Конечной целью для себя я поставил реализацию объектов языка OVAL и системы их хранения и индексации.

Первоочередным вопросом был выбор способа хранения собранной информации. Первым (и неудачным) решением стал SQLite 3. Я не буду особо задерживаться на этом печальном опыте, но как показала практика, — хранение непредсказуемого дерева в реляционной базе данных слишком сложная задача для меня. Поэтому я обратил внимание на базы данных NoSQL. Так как я планировал ограничиться монопольным использованием базы, мой выбор пал на ZoDB. Конечно же, это решение тоже было поспешным. Но переделывать на MongoDB было уже поздно. Так что для дальнейших выкладок кода хотелось бы отметить, что PersistentMapping() — это, по сути, обычный dict(). Приняв во внимание неопределенность восприятия хэшмапов базой данных, в качестве хэшмапов я использовал ключи того же класса. PersistentList() — это эквивалент list(). Данная замена необходима для хранения подобных структур в ZoDB. Это обусловлено внутренней логикой работы базы данных. Для сохранения класса в базе данных целиком необходимо, что-бы класс являлся наследником класса persistent.Persistent.

Кроме того, прошу заранее меня простить за не-шаблонное именование переменных, методов и классов и возможную «косорукость»: я не волшебник, я только учусь. И с удовольствием приму любые правки и замечания от более осведомленных коллег.

Во избежание цитирования излишне больших кусков кода сразу привожу ссылку на исходник на code.google.com.

В целом при анализе структур языка OVAL я отметил, что его главной особенностью является расширяемость. Если необходимо ввести собственную структуру, достаточно описать ее в XSD, определяющим ваш namespace. Поскольку структуры в большинстве своем представляют типовые наборы данных, то я решил создать выделенный класс для каждого объекта языка. Основная цель такой реализации — создание контейнера для информации, который будет удобно закладывать в БД. Таким образом, в качестве базового объекта выступил элемент OBJECT:

# UNIVERSAL OVAL OBJECTS
class oval_object(persistent.Persistent):
    def __init__(self):
        self.id = str()
        self.tag = "object"
        self.namespace = str()
        self.oval = str()
        self.version = 0
        self.vHash = 0
        self.deprecated = False
        self.variables = PersistentList()
        self.notes = PersistentList()
        self.comment = str()
        self.signature = str()
 
    def assign_id(self, UniqId):
        self.id = "oval:"+self.oval+":obj:"+str(UniqId)
 
    def hash(self):
        summ_hash = 0
        summ_hash += hash(self.tag)
        summ_hash += hash(self.namespace)
        summ_hash += hash(self.comment)
        for var in self.variables:
            if isinstance(var, variable):
                summ_hash += var.hashme()
            else:
                raise Exception("Error input parameters in object variables")
        return summ_hash
 
    def hashme(self):
        self.vHash = self.hash()
        return self.vHash
 
    def clearme(self):
        hashmap = PersistentMapping()
        for var in self.variables:
            var.clearme()
            var_hash = var.hashme()
            if var_hash not in hashmap.keys():
                hashmap[var_hash] = None
            else:
                self.variables.remove(var)
 

Данный класс является базовым для всех основных элементов OVAL.Definition, Test, State и Oval_variable будут являться наследниками этого класса. Для того что-бы реализовать возможность сравнения элементов, были созданы методы hash() (универсальный для всех) и hashme()(переопределяемый у потомков).
Как видно из названий переменных, все основные структуры заданы статически. Динамическими же являются возможные «потомки», структуры которых мы не знаем. Для реализации таких «потомков» я сделал класс Variable, во многом повторяющий по идеологии Element из ElementTree:

class variable(persistent.Persistent):
    def __init__(self, tag=str(), body = str(), attributes = None, variables = None):
        if not attributes:
            self.attributes = PersistentMapping()
        if not variables:
            self.variables = PersistentList()
        if attributes and not isinstance(attributes, PersistentMapping):
            attributes = PersistentMapping(attributes)
        if variables and not isinstance(variables, PersistentList):
            variables = PersistentList(variables)
        self.tag = tag
        self.body = body
        if attributes:
            self.attributes = attributes
        if variables:
            self.variables = variables
        self.vHash = 0
 
    def clearme(self):
        hashmap = PersistentMapping()
        for var in self.variables:
            var.clearme()
            var_hash = var.hashme()
            if var_hash not in hashmap.keys():
                hashmap[var_hash] = None
            else:
                self.variables.remove(var)
 
    def hashme(self):
        summ_hash = 0
        summ_hash += hash(self.tag)
        summ_hash += hash(self.body)
        if self.attributes:
            for attribute in self.attributes.keys():
                summ_hash += hash(attribute) + hash(self.attributes[attribute])
        if self.variables:
            for var in self.variables:
                if isinstance(var, variable):
                    if var !self:
                        summ_hash += var.hashme()
                else:
                    raise Exception("Error input parameters in variables. Self append ?")
        self.vHash = summ_hash
        return self.vHash

Как и в случае с OBJECT, потребовалась функция расчета хэша. Структура класса максимально подогнана к ElementTree.Element, что бы упростить в будущем выгрузку базы данных в XML-формат.

В целом получился набор объектов, удобных для заполнения. Но мало их заполнить: при создании собственных объектов необходимо выполнять требования регулятора (в нашем случае MITRE) к заполнению и храненю новыхDefinition. Соответственно, мне потребовалась система индексации и контроля изменения версии Definition. Для этого был создан класс oval_suite(). В нем реализован базовый набор методов (put, get, delete, search, import_xml, export_xml), который облегчает работу с контентом и позволяет не заморачиваться на вышеупомянутых проблемах.

Одна из специфичных проблем, с которыми я столкнулся в процессе создания oval_suite(),- нехватка памяти при попытке экспортировать в XML-формат большой объем данных (порядка 100Мб) за один заход. Поэтому для реализации этой функции пришлось создавать конечный файл поэтапно и использовать функцию удаления элементов ElementTree.Element, позаимствованную из StackOverflow:

def _garbager(self, root):
        for element in list(root):
            self._garbager(element)
        root.clear()
        del root

В целом и общем, получились наброски инструментария для работы с OVAL на Python, который может пригодиться при сборке и создании секьюрити-контента. Данный модуль позволяет выполнять сборку секьюрити-контента и «упаковку» его в базу данных с контролем версий и уникальности идентификаторов.

Спасибо за внимание! Надеюсь, кто-нибудь заинтересуется тематикой OVAL и данные наработки окажутся ему полезны.

Автор: isox

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js