Chapter 2

2. Developer Documentation

You will find two main parts in this documentation, the first deals with the management and creation of playbooks, the second part, deals with the software architecture and the code concerning the front/back server.

Subsections of 2. Developer Documentation

Chapter 1

AutomA Playbooks

Introduction

This chapter deals with the documentation on the part of the playbooks and the questions for all the recommendations made available to users. You will find the project AutomA-Playbooks on github as well as the procedure to follow to contribute to the project in the section Contribute.

Tree structure

The tree structure of the github repository is divided into the following form:

└── KERNEL
    └── OS
        └── VERSION
            ├── CATEGORY
            │   ├── REFERENCE
            │   │   └── LEVEL
            │   │       └── RXX_ACTION_NAME
            │   │           ├── playbook.yml.j2
            │   │           └── questions.yml
            │   └── REFERENCE2
            │       └── LEVEL2
            │           └── CXX_ACTION_NAME2
            │               ├── playbook.yml.j2
            │               └── questions.yml
            ├── CATEGORY2
            │   └── REFERENCE
            │       └── LEVEL
            │           └── RXX_ACTION_NAME3
            │               ├── playbook.yml.j2
            │               └── questions.yml
            └── CATEGORY3
                └── REFERENCE2
                    └── LEVEL
                        └── RXX_ACTION_NAME4
                            ├── playbook.yml.j2
                            └── questions.yml

As of November 30, 2023, the project looked like the following tree:

└── LINUX
    └── DEBIAN
        └── 12
            ├── KERNEL
            │   └── ANSSI
            │       └── 1_INTERMEDIATE
            │           └── R11_CONFIGURE_YAMA_LSM
            │               ├── playbook.yml.j2
            │               └── questions.yml
            ├── NETWORK_STACK
            │   └── ANSSI
            │       └── 1_INTERMEDIATE
            │           └── R13_DISABLE_IPV6
            │               ├── playbook.yml.j2
            │               └── questions.yml
            ├── PACKAGE_MANAGEMENT
            │   └── ANSSI
            │       └── 0_MINIMAL
            │           └── R61_PERFORM_REGULAR_UPDATES
            │               ├── playbook.yml.j2
            │               └── questions.yml
            ├── PERMISSIONS
            │   └── ANSSI
            │       ├── 0_MINIMAL
            │       │   └── R54_ACTIVATE_STICKY_BIT
            │       │       ├── playbook.yml.j2
            │       │       └── questions.yml
            │       └── 3_REINFORCED
            │           └── R36_CHANGE_UMASK_VALUE
            │               ├── playbook.yml.j2
            │               └── questions.yml
            └── USERS
                └── ANSSI
                    └── 0_MINIMAL
                        └── R30_DISABLE_UNUSED_USER_ACCOUNTS
                            ├── playbook.yml.j2
                            └── questions.yml

We thought about it and chose this folder structure to allow for better integration of future hardening rules and environments. The principle of this slightly substantial structure is to allow better modularity of the project.

Do you want to contribute to the project by adding hardening rules for Windows Server 2022?

You must create the tree (if it does not exist), here /WINDOWS/SERVER/2022/. Then you need to create the following structure according to the hardening actions you want to add. Genericly, here are the folders to create (in order):

  1. CATEGORY : The name of the category that the hardening rule fits into. It is possible that toughening actions could be in several categories. In this case, choose the category in which it would be the most logical but in case of questions, you can open an issue on the Github project or by email at automa.project@proton.me.
  2. REFERENCE : The reference frame in which the hardening action is taken. We base all of our actions on the recommendations of ANSSI but it is possible to use other repositories such as the CIS.
  3. LEVEL : This file is taken from the ANSSI level system in its guide to Security Recommendations relating to a GNU/Linux system. In this guide, they offer a grid of hardening levels which therefore allows you to locate the level of hardening action. It is necessary to carefully choose the level of hardening of the hardening rules to enable better segmentation and user experience. The possible levels are as follows:
    • 0_MINIMAL
    • 1_INTERMEDIATE
    • 2_REINFORCED
    • 3_HIGH
  4. HARDENING_ACTION : The name of the hardening action preceded by a non-necessarily unique identifier. In the case of Security Recommendations relating to a GNU/Linux system, the identifier consists of an R followed by a number, for example R30. The goal is to keep the same nomenclature for the same reference system.

When your folders are created, two files playbook.yml.j2 and questions.yml are no longer missing. The contents of these files will be described in parts playbook.yml.j2 and questions.yml.

Subsections of AutomA Playbooks

Playbooks testing

Once the playbooks have been created, they need to be tested to ensure that the hardening action has been carried out correctly. To simplify the process, we provide Dokcer containers for playbook testing. Each environment must have its own container with the correct version. For example, even if Debian 11 and Debian 12 are close in terms of operation, it’s still necessary to separate the two versions into two different docker images.

Note

It is likely that some hardening actions cannot be tested on a containerized environment. It will therefore be necessary to run the tests on a virtual machine.

Existing Docker images

Docker images can be found here. It will only be necessary to perform a pull.

Missing Docker images

Make a request

You can send us a request by e-mail or open an issue or discussion on AutomA’s Github.

Creating the image

In this section, we’ll deal with the example of a Debian 12 image, but the process will remain the same whatever the environment you’re using.

Prerequisites

The following list describes all the components required to create an image:

  • python3
  • python3-pip
  • python3-venv
  • sshpass

Then execute following commands :

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install ansible-core

Dockerfile

In the file named Dockerfile:

FROM debian:12

RUN apt-get update -y && \
    apt-get install openssh-server sudo python3 -y

RUN sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin Yes/" /etc/ssh/sshd_config

RUN useradd -m -s /bin/bash user && \
    echo 'user:password!' | chpasswd && \
    echo 'root:password!' | chpasswd && \
    service ssh restart

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]

To build your image: docker build -t automa-debian12 . (Do not forget the dot at the end !)

Once your build command is finished, you can instanciate it with:

docker run -d -p 2222:22  --name debian-ssh-container automa-debian12

Required files

inventory.yml

This file gives our container configurations to Ansible.

all:
  hosts:
    docker-debian12:
      ansible_host: 127.0.0.1
      ansible_port: 2222
      ansible_user: user
      ansible_password: password!
      ansible_become: yes
      ansible_become_method: sudo
      ansible_become_user: root
      ansible_become_password: password!

playbook.yml

The playbook file is the one that will be given to Ansible so that it can apply a rule to the container. Here’s an example:

---
- name: "Disable unused user accounts"
  hosts: "all"

  tasks:

    - name: "List all users"
      ansible.builtin.getent:
        database: "passwd"
        split: ":"
      register: "all_users"

    - name: "Disable user"
      ansible.builtin.user:
        name: "{{ item }}"
        state: "absent"
      with_items: "{{ all_users.ansible_facts.getent_passwd }}"
      when:
        - item not in ['root','user','_apt','sshd']

Testing !

You can execute the following command:

python3 -m ansible playbook -i inventory.yml -l all playbook_example.yml -vvv

playbook.yml.j2 file

The playbooks are jinja templates of Ansible playbooks. This allow us to render playbooks regarding the user probided parameters.

Example

---
- name: "Example rule"
  hosts: "all"

  tasks:

    - name: "Disable user"
      ansible.builtin.user:
        name: {% raw %}"{{ item }}"{% endraw %}
        state: "absent"
      with_items: "{{ used_users }}"

This rule is mixing AutomA jinja rendering and Ansible jinja rendering. The used_users variable is the one provided by the questions.yml, thus it will be hardcoded in the playbook. For the item variable, it is enclosed in raw jinja balises to enforce the no-rendering of this variable by the AutomA renderer as it is an Ansible rendered variable.

questions.yml file

This file contain the specification of the useful variables for the playbook jinja template.

Example

---
id: "The ID of the rule"
title: "The title of the Rule"
description: "The description of the rule"
author: "The author name"
last_modified: "MM/DD/YYYY of the corresponding last modifiction"
tags:
  - "Tag1"
  - "Tag2"

questions:
  - title: "The first value to fill"
    name: "The name of the variable to render with this result"
    required: "A boolean to say if the value can be None or not"
    type: "One of the defined types"
    value: "None by default, the value filled by this question"

  - title: "The first value to fill"
    name: "The name of the variable to render with this result"
    required: "A boolean to say if the value can be None or not"
    type: "One of the defined types"
    value: "None by default, the value filled by this question"

### EXAMPLE ###
---
id: "R42"
title: "This rule is an example"
description: "You will answer a famous question: what is the life answer ?"
author: "aiglematth"
last_modified: "10/24/2023"
tags:
  - "life"
  - "answer"

questions:
  - title: "What is the answer of the life ?"
    name: "life_answer"
    required: yes
    type: "u8"
    value:

  - title: "Are you sure of your answer ?"
    name: "are_you_sure"
    required: yes
    type: "bool"
    value:

Types

TypeDescription
strBasic string type
list<x>List of type x
choice<x>[y1,y2,...]List y1, y2, … choices of type x
Chapter 2

AutomA WebUI

You will find all documentations about function used in front and back in the WebUI project. The two technologies used are Python for backend and Javascript for frontend:

Subsections of AutomA WebUI

Chapter 1

Back End

All python documentation are generated with the pydoc-markdown

Subsections of Back End

utils.supported_systems.py

SingletonSupportedSystems Objects

class SingletonSupportedSystems()

This class is a sigleton object for SupportedSystems class

SupportedSystems Objects

class SupportedSystems(SingletonSupportedSystems)

This class saves and checks path of the env selected by user.

reset_params

def reset_params()

Reset varibles of path, used when they are errors

get_entire_path

def get_entire_path()

The method checks and return the complete environment path else raise Exception

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

Returns:

  • str - The complete environment path selected by the user

set_playbooks_location

def set_playbooks_location(path: str)

Check and set the location of the playbook directory

Arguments:

  • path str - The path of the playbook location

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

get_os

def get_os() -> list[str]

Search for OS directories contained in the playbook directory

Raises:

  • VariablePathNotDefined - If variable are not filled

Returns:

  • list[str] - The list of OS availables in {playbook}/

get_os_type

def get_os_type() -> list[str]

Search for OS type directories contained in the OS directory selected

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

Returns:

  • list[str] - The list of OS type availables in {playbook}/{OS}/

get_os_version

def get_os_version() -> list[str]

Search for OS version directories contained in the OS type selected

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

Returns:

  • list[str] - The list of OS version availables in {playbook}/{OS}/{OS_TYPE}/

set_os

def set_os(os: str) -> None

Set the OS selected by the user

Arguments:

  • os str - OS name selected

Raises:

  • VariablePathNotDefined - If variables are not filled

set_os_type

def set_os_type(os_type: str)

Set the OS type selected by the user

Arguments:

  • os_type str - OS type name selected

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

set_os_version

def set_os_version(os_version: str)

Set the OS version selected by the user

Arguments:

  • os_version str - OS version name selected

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

utils.recommendations_selected.py

SingletonRecommendationsSelected Objects

class SingletonRecommendationsSelected()

This class is a sigleton object for RecommendationSelected class

RecommendationsSelected Objects

class RecommendationsSelected(SingletonRecommendationsSelected)

This class keep in memory which recommendations has been selected

utils.questions_parser.py

read_questions_file

def read_questions_file(path: str) -> dict

Take a recommendation path and read the questions.yml file linked to

Arguments:

  • path str - Path of the recommendation

Raises:

  • PathDoesNotExist - If the path {path}/questions.yml does not exist

Returns:

  • dict - A dict that represents the questions.yml file

list_categories

def list_categories(supported_systems: SupportedSystems) -> list[str]

List categories contained in the environment selected by the user

Arguments:

  • supported_systems SupportedSystems - singleton that contains the user env selection

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

Returns:

  • list[str] - The list of categories in the path

list_reference

def list_reference(category: str,
                   supported_systems: SupportedSystems) -> list[str]

List all reference base (ANSSI, CIS, etc) from a category

Arguments:

  • category str - The category to list
  • supported_systems SupportedSystems - singleton that contains the user env selection

Returns:

  • list[str] - the list of references contained in the category

list_recommendations

def list_recommendations(category: str, reference: str,
                         supported_systems: SupportedSystems) -> list[str]

List recommendation available in the reference directory in a category

Arguments:

  • category str - One of the category available in env selected
  • reference str - The reference to list
  • supported_systems SupportedSystems - singleton that contains the user env selection

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

Returns:

  • list[str] - The list of recommendations in the reference dir from the category

is_type_ok

def is_type_ok(type_asked: str, answer) -> bool

This method check is the type provided by the user is correct

Arguments:

  • type_asked str - type asked in the questions.yml file
  • answer type - answer provided by the user

Returns:

  • bool - True if the type corresponds, else False

check_answers

def check_answers(r_path: str, answer_list: list[dict]) -> dict[str]

Take the answer provided by the user and check if it is conform in comparaison of the questions.yml. It check, the type, the real format, if value exists in case of required “true”. If everything is correct, return the dict object to inject in the playbook template (playbook.yml.j2).

Arguments:

  • r_path str - path of the recommendation
  • answer_list list[dict] - list of the answers provided by the user

Raises:

  • AnswerIsRequired - If the answers is present but no value
  • WrongAnswerType - If the type provived textually of in object instance is wrong
  • PathDoesNotExist - If the specified path does not exist
  • IndexError - If there are missing answers

Returns:

  • dict[str] - The answers the inject in playbook template

utils.playbook_runner.py

run_ansible_playbook

def run_ansible_playbook()

This function call runner function from ansible to run the playbook.master.yml with the inventory.yml

Raises:

  • PathDoesNotExist - If the path of playbook.master.yml or inventory.yml does not exist

utils.playbook_renderer.py

playbook_render_write

def playbook_render_write(dir_path: str, variables: dict)

This function take the playbook.yml.j2 template to inject into all answers from user input. After this, the function is writing the playbook as ‘playbook.yml’ in the directory

Arguments:

  • dir_path str - The path of the recommendation where template is stored
  • variables dict - A dict containing variable names and variable values to render in the template

Raises:

  • PathDoesNotExist - If the specified path does not exist

utils.path.py

list_dir_in_dir

def list_dir_in_dir(path: str) -> list[str]

This method is a os.listdir wrapper to return only directories without the .git dir

Arguments:

  • path str - Dir to list

Returns:

  • list[str] - List of the directories contained in path

utils.id_management.py

SingletonRecommendationID Objects

class SingletonRecommendationID()

Sigleton of the RecommendationID class

RecommendationID Objects

class RecommendationID(SingletonRecommendationID)

This class manage the ID of each recommendation. To avoid to put ID in recommendation directories and files, the class RecommendationID manage dynamically ID by adding missing pair ID/path in the ID file. Futhermore, all ID are UUID from the uuid.uuid4()

set_playbooks_location

def set_playbooks_location(path: str)

Check and set the location of the playbook directory

Arguments:

  • path str - The path of the playbook location

Raises:

  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

set_id_file_location

def set_id_file_location(path: str)

Set the location of the ID/PATH pair file

Arguments:

  • path str - Path of the file

Raises:

  • VariablePathNotDefined - If variables are not filled

attribute_new_playbooks

def attribute_new_playbooks(all_recommendation_paths: list[str])

Add missing pair ID/PATH in the file. The pair ID/PATH are not deleted when a playbook is removed.

Arguments:

  • all_recommendation_paths list[str] - list of all recommendation paths

get_available_playbooks

def get_available_playbooks() -> list[str]

browse all folders in the playbook folder to retrieve all recommendation paths

Returns:

  • list[str] - all recommendation paths

get_id_from_path

def get_id_from_path(path: str) -> str

Translate a path to an ID. The ID is used mainly in the front-end

Arguments:

  • path str - path to translate

Raises:

  • IDDoesNotExist - If the path doesn’t have an ID
  • PathDoesNotExist - If the specified path does not exist
  • VariablePathNotDefined - If variables are not filled

Returns:

  • str - The path’s ID

get_path_from_id

def get_path_from_id(id: str) -> str

Translate an ID to a path. The path is used mainly in the back-end

Arguments:

  • id str - The id to translate

Raises:

  • IDDoesNotExist - If the ID does not exist
  • VariablePathNotDefined - If variables are not filled

Returns:

  • str - The ID’s path

utils.hosts_selected.py

SingletonHostsSelected Objects

class SingletonHostsSelected()

This class is a sigleton object for HostsSelected class

Host Objects

class Host()

This class represents one host object to export it to yml for the ansible inventory

__init__

def __init__(hostname: str, host_ip: str, host_port: int)

Create Host instance and fill hostname, host_ip and host_port

Arguments:

  • hostname str - The name of the host
  • host_ip str - The ip or fqdn of the host
  • host_port int - The ssh port of the host

Raises:

  • ValueError - If there are missing value, raise the Exception

set_connection_method

def set_connection_method(connection_method: int, username: str,
                          pass_or_keyfile: str)

Fill connection_method, username and pass_or_keyfile.

Arguments:

  • connection_method HostConnectionMethod - Value from the Enum, define user/password or user/keyfile connection method
  • username str - user to connect on host using ssh
  • pass_or_keyfile str - password or the path of the keyfile to connect on host using ssh

Raises:

  • ValueError - If there are missing value, raise the Exception

set_sudo_access

def set_sudo_access(sudo_username: str, sudo_password: str)

Fill sudo_username and sudo_password to permits privilege escalation

Arguments:

  • sudo_username str - username of a user with sudo privilege
  • sudo_password str - password of a user with sudo privilege

Raises:

  • ValueError - If there are missing value, raise the Exception

get_yml

def get_yml() -> str

Render the Host instance into a string with yml syntax for the Ansible inventory file

Raises:

  • ValueError - If the value of connection_method is not in the Enum

Returns:

  • str - The yml string

HostsSelected Objects

class HostsSelected(SingletonHostsSelected)

This class keep in memory which hosts are selected and their configuration

add_host

def add_host(host: dict)

Create a host and add it to the list of hosts

Arguments:

  • host dict - Dict that contains value to add host

Raises:

HostAlreadyAdded : If the hostname already exists

  • ValueError - If there are missing value, raise the Exception

is_hostname_unique

def is_hostname_unique(new_hostname: str) -> bool

Check if the hostname has already been added

Arguments:

  • new_hostname str - The hostname to check

Raises:

  • ValueError - If there are missing value, raise the Exception

Returns:

  • bool - True if the hostname is unique else False

utils.configuration.py

SingletonConfiguration Objects

class SingletonConfiguration()

Sigleton of the Configuration class

Configuration Objects

class Configuration(SingletonConfiguration)

This class read configuration file and retrieve variables. If a variable is not present the variable is set with a default value.

get

def get(config_key)

return the config value of the key specified in arg

read_configuration

def read_configuration()

Read the configuration file and set required variables

Chapter 2

Front End

You will find here the javascript documentation from the frontend. Documentation has been generated with jsdoc2md

Subsections of Front End

app.table.component.js

Functions

renderRecommendationLine(item)

Render recommendation line adding elements to the DOM

ParamTypeDescription
itemRecommendationRecommendation retrieve by the back

generateButtonRadio(line, id) ⇒ HTMLElement

Generate button radio and fill it from local storage

Returns: HTMLElement - th element containing a element with radio.

ParamTypeDescription
lineRecommendationRecommendation line HTML
idstringID Item

unSavedIdSelected(id)

Unsave id from the local storage

ParamTypeDescription
idstringID Item

renderInventoryLine(host)

Render inventory line adding elements to the DOM

ParamTypeDescription
hostInventoryHost from inventory

renderCell(textDisplayed)

Render cell

ParamTypeDescription
textDisplayedRecommendationText added in the celle

app.selection.component.js

Functions

fillSelector(type, values, verificationFunction)

Fill selector with values and bind verification function

ParamTypeDescription
typetypename of the selector
valuestypevalues
verificationFunctiontypevalues

loadDataFromStorage(select_item, selectorId)

Load data of the selector from storage

ParamTypeDescription
select_itemM.FormSelectSelect element
selectorIdstringid of the selector

storeInLocalStorage(value, selectorId)

Store data of the selector into storage

ParamTypeDescription
valuestringvalue to store
selectorIdstringid of the selector

removeValidButtonAttribute(attributeName)

Remove an attribute to valid button

ParamTypeDescription
attributeNamestringName of the attribute

setValidButtonDisabledAttribute(bool, attributeName)

Change disabled attribute in validation button

ParamTypeDescription
boolbooleanvalue of disabled attribute
attributeNamestringName of the attribute

reinitSelector(selector)

Reinit selector options, remove all options except the first one.

Summary: Remove all options in the dom except the first because in our case this first option is used as a placeholder for the moment

ParamTypeDescription
selectorM.FormSelectselector to clean

resetChildSelector(childName)

Reset child

Summary: In our workflow reset a child selector has to reinit a child ONLY if the child is already visible

ParamTypeDescription
childNamestringchild name selector

app.question.component.js

Functions

renderQuestion(_id)

If the modal template is already instatiated, the function open it else it creates the modal. It takes the configuration of the question to choose which elements need to be added to the modal (select, input, chips, …)

Summary: Display a modal template and fill it from config question

ParamTypeDescription
_id_idquestion UUID

getAnswersValues(id, indexQuestion) ⇒ answer

Get answer value by id recommendation and index question from local storage

Returns: answer - answer - if exists or null

ParamTypeDescription
idstringid of recommendation
indexQuestionnumberindex of the question

initializeMaterializeComponent()

Initialize Material components. This function need to be called after adding components to the DOM. This method is useful because in our case, our components are returned by our functions before adding them to the DOM. Some errors appears with Select component with this workflow. It’s base on global scope variable ‘MATERIALIZE_FIFO’

Summary: Initialize Material components wich need to be added to the DOM before init

renderQuestionBtn()

Create validation button and bind it with the click event.

renderQuestionField(_id, index, one_field, answerStored) ⇒ HTMLElement

Render question from ‘render_field_str’ method in case of ‘str’ : input Render question from ‘render_field_list_str’ method in case of ’list’ : chips Render question from ‘render_field_choice_str’ method in case of ‘choice’ : select

Summary: Render question depending on the answer type
Returns: HTMLElement - HTMLElement - Brief description of the returning value here.

ParamTypeDescription
_id_idUUID of the recommendation
indexindexIndex of question
one_fieldone_fieldconfiguration of the answer
answerStoredanswerStoredanswer if exists or null

renderFieldStr(_id, index, one_field, answerStored) ⇒ HTMLElement

Render question method in case of ‘str’ : it creates an input

Returns: HTMLElement - HTMLElement - Brief description of the returning value here.

ParamTypeDescription
_id_idUUID of the recommendation
indexindexIndex of question
one_fieldone_fieldconfiguration of the answer
answerStoredanswerStoredanswer if exists or null

renderFieldListStr(_id, index, one_field, answerStored) ⇒ HTMLElement

Render question method in case of ’list’ : it creates a chips element

Returns: HTMLElement - HTMLElement - Brief description of the returning value here.

ParamTypeDescription
_id_idUUID of the recommendation
indexindexIndex of question
one_fieldone_fieldconfiguration of the answer
answerStoredanswerStoredanswer if exists or null

renderFieldChoiceStr(_id, index, one_field, answerStored) ⇒ HTMLElement

Render question method in case of ‘choice’ : it creates a select element The Select element is an

Returns: HTMLElement - HTMLElement - Brief description of the returning value here.

ParamTypeDescription
_id_idUUID of the recommendation
indexindexIndex of question
one_fieldone_fieldconfiguration of the answer
answerStoredanswerStoredanswer if exists or null

retrieveListStrData(inputComponent, configType)

Get data from chips in case of ’list’, it takes values from a chips elements and add it to the local storage

ParamTypeDescription
inputComponentinput_componentChips element
configTypeconfigTypeConfiguration of the field

retrieveStr(inputComponent, configType)

Get data from input in case of ‘str’, it takes valueand add it to the local storage

ParamTypeDescription
inputComponentinput_componentChips element
configTypeconfigTypeConfiguration of the field

storeAnswerInLocalStorage(inputComponent, dataToSave, configType)

Update local storage with a new value. If the answer value is empty, it’s remove from local storage. If there’s already a value present, we update this value.

Summary: Update local storage with a new value.

ParamTypeDescription
inputComponentinputComponentinputComponent is a DOM Element, it has an id : UUIDRecommendation-index
dataToSavedataToSaveData already process for the back, this value will be sent
configTypeconfigTypeconfig type of the question

storeSelectedIds(id)

Manage selected IDS. Based on verification about storage item with UUID of the recommendation. If an item with the same id does not exists, we remove the id from the data We also check the local storage to create it if it’s not exists.

Summary: Manage selected IDS stored in store

ParamTypeDescription
idParamDataTypeHereID of recommendation

storeAnswerData(id, valueFormat, dataToSave)

Store answer data to localStorage

ParamTypeDescription
idinputComponentId of recommendation
valueFormatinputComponentData formated by our function to be store
dataToSaveinputComponentdata to check if it’s not empty in order to remove local storage

formatValueToStore(inputComponent, dataToSave, configType) ⇒ string

Format a value in order to be store in local storage. It’s a JSON format with three keys : value, formatType, index

Returns: string - Object - id, valueFormat

ParamTypeDescription
inputComponentinputComponentInput component is needed to retrieve the index of answer and id recommendation
dataToSaveinputComponentData in value
configTypeinputComponentConfiguration of this answer to send type answer to the back

Contribute

Purpose

The purpose of this document is to propose a procedure to follow when developing on the AutomA project. This procedure applies whether you are internal or external to the project.

Procedure

A - ISSUE

Whatever github repository you want to work on, you need to create an issue by putting the necessary elements in it. The issue must imperatively be written in English. Let’s take the following example: if you want to create a new ANSSI recommendation, such as R30, you need to fill in the following fields:

FieldContent
Title[Make-Rule] R30 - Disable unused user accounts
CommentTodo : questions.yml + playbook.yml From : ANSSI Rule : 30 Level : Minimal

In addition, if you are in the project, you must add :

FieldContent
Assigneesthe person(s) working on it
Labels[TODO]+[enhancement]
ProjectKanban (remember to put the project into TODO as soon as you create it)

B - BRANCH

Case: You’re working on the project’s source repository

From the issue page, you can create a development branch associated with it. Simply click on “Create branch” to display a pop-up window requesting several items of information. You don’t need to change the default information.

CreateBranch1 CreateBranch1

CreateBranch2 CreateBranch2

You now have a branch to develop! Don’t forget to change branch (on vscode, click bottom left). Here are the commands you need to issue in your terminal:

# To retrieve changes to the repository, including the new branch
git fetch --all
# Change development branch
git checkout <your_branch_name>
# Check
git branch --show-current

Case: You’re working on a project fork

You need to fork the project on your account, which will give you the rights to modify the code. Once on your repository, you can either develop directly on your main branch or create auxiliary branches as we do on the official repository.

C - MERGING

Updating your code

Case: You’re working on the project’s source repository

Before requesting to push your modifications onto the main branch, you need to make sure that you won’t overwrite the work of other contributors. To do this, you must first update your branch to the same level as the main branch.

From your development branch, perform the following commands (in order):

git checkout <your_branch_name>
git fetch
git merge origin/main
# Conflict management on your branch
# (on the command line or via your IDE)
git push

Case: You’re working on a project fork

You must add the official repository as an additional remote.

git remote add source <repo_url>

Update your branch :

git pull source <your_branch_name>

You need to manage any conflicts that may arise, while taking care not to break any existing code. Now that your local repository is up to date, it’s time to update your remote repository:

git push 

Merge request

From the github web interface, you can perform a pull-request to merge your branch with the main branch. Once the minimum number of reviewers has been reached, you can validate the merge.