AutomA Wiki

Welcome to the AutomA project documentation site. You’ll find a wealth of information in the following sections:

The project

This project is aimed particularly, but not exclusively, at system and network administrators, CISOs and anyone wishing to improve the security of their infrastructures. We initiated this project following the observation that hardening machines is time-consuming in many companies, and that it’s not always easy to apply hardening rules.

This project is available under the MIT license.

References

The French Cybersecurity Agency (ANSSI) is made up of experts, particularly in operating system hardening. We therefore chose ANSSI as the first repository for our hardening actions. Of course, we’d like to propose other standards such as CIS. If you would like to suggest other standards to add, please contact us at the following e-mail address automa.project@proton.me.

Contribute

You can contribute to the project in several ways! You can :

Subsections of AutomA Wiki

Subsections of 1. User Guide

Getting started

AutomA is an automated operating system hardening project, based on the rules and advice of leading cybersecurity authorities such as ANSSI. The aim of this user guide is to help you get to grips with the Web interface and configure the SSH service.

Home Page Home Page

Environment selection

After clicking on Start on the home page, you will be asked to select the environment of the machines to be hardened.

Environment Selection Environment Selection

Info

You can only harden one type of environment at a time

Host

The machines to be secured must be entered in the HOST INVENTORY tab. This tab is used to define all the machines to be secured.

Host Inventory Tab Host Inventory Tab

You must enter the following information:

  • Name: A unique arbitrary name for your machine
  • IP: Your machine’s IP address or FQDN (example: samba.local).
  • Port: Your machine’s SSH listening port
  • Connection Method: You can choose between password and key.
  • Username : User name for SSH connection to your machine
  • Auth : The password or path to your associated private key
  • Sudo Username: The username for elevation of privilege
  • Sudo Password : The password of the user with administrative rights.

Here is an example configuration:

Host Inventory Tab Host Inventory Tab

You can also modify every fields by clicking on it :

Host Inventory Tab Host Inventory Tab

Generating actions

In the HARDENING ACTION tab, a list of hardening actions is available. These rules are classified according to :

  • One of the following rule categories:
    • KERNEL
    • LOGGING
    • MEMORY
    • MONITORING
    • NETWORK_STACK
    • PACKAGE_MANAGEMENT
    • PARTITIONING
    • PERMISSIONS
    • SERVICES
    • USERS
  • The recommendation level of the
    • MINIMAL
    • INTERMEDIATE
    • REINFORCED
    • HIGH
  • Rule reference (ANSSI, NIST, etc …)

You can select a rule by clicking on it and validating:

Action Selection Action Selection

Some rules require additional information from the user to define the appropriate behavior.

For example, this rule enables automatic updates at a frequency that the user can select. A drop-down menu appears with a list of possible choices:

Action Selection With Input Action Selection With Input

Here, we have selected monthly:

Action Selection With Input Selected Action Selection With Input Selected

Run actions

Once the configuration is complete, the user must generate his configuration by clicking on the GENERATE button.

Then press the RUN button to launch the rules on the configured machines. Alternatively, click on the arrow to display the DOWNLOAD button, enabling you to retrieve all files for manual launch.

This allows us to observe the execution of playbooks and actions by generating logs directly on the interface :

Log View Log View

Configure Information System

We use Ansible to propagate hardening actions, so we need to open an ssh port for Ansible to perform the necessary actions. On this page, you’ll find the information you need to set up an SSH server on a Debian 12 Linux machine.

Configuring the SSH service

Status check

The following command is used to check the status of the OpenSSH service:

sudo systemctl status ssh

OpenSSH Status OpenSSH Status

Service activation

If the service is disabled, use the following command to start it:

sudo systemctl start ssh

Startup service

On most Linux systems, the SSH service starts at boot time. If this isn’t the case and you’d like this behavior, use the following command to enable it at machine startup:

sudo systemctl enable ssh

Configuration du service

The /etc/ssh/sshd_config file is used to configure the SSH daemon. By default, the service runs on port TCP/22.

It is recommended to :

  • Disable root account login
  • disable password login, preferring key login
  • Disable listening on IPv6 if not in use
  • Disable X11 forwarding
  • Change listening port (default 22)
# OpenSSH config file
Port 50122 # Set the port you want
ListenAddress 0.0.0.0 # Listen on IPv4

# To disable IPv6, you need to comment the following line
#ListenAddress ::

PubkeyAuthentification yes
PermitRootLogin no
PasswordAuthentification no
PermitEmptyPassowrd no

X11Forwarding no

Key generation

To generate a key pair, SSH includes the following command:

ssh-keygen -t ecdsa -b 521 -f /home/user1/.ssh/id_ecdsa_debian12
Note

We strongly recommend protecting your private key with a password!

This generates two files, id_ecdsa_debian12 which contains the private key, and id_ecdsa_debian12.pub which contains the public key. Both files are stored in the ~/.ssh folder.

Key usage

The machine’s private key is required to use key authentication in AutomA. Please generate the keys on the machine hosting AutomA and add the corresponding public key to the machine’s ~/.ssh/authorized_keys file.

There are several ways of putting your public key on the remote machine:

SSH-COPY

You can use the ssh-copy binary as follows:

ssh-copy-id -i ~/.ssh/<your_key>.pub <username>@<ip_address> -p <port>

This technique only works if the SSH server accepts a connection using password.

USB Stick

You can copy your public key to a usb key that you have mastered. Then, on the destination machine, create the ~/.ssh folder and the ~/.ssh/authorized_keys file into which you copy the contents of the public key from your USB key.

The right permissions must be applied:

chmod 700 /home/user/.ssh
chmod 600 /home/user/.ssh/authorized_keys
chown -R user:user /home/user/.ssh

Subsections of References

ANSSI - Linux

last update : December 8, 2023

Debian 12

Applicability

%%{
    init: {
        "theme": "base",
        "themeVariables": {
            "pie1": "#b6d7a8",
            "pie2": "#e06666",
            "pie3": "#ffe599"
        }
    }
}%%
pie title Applicability rate in AutomA
    "YES" : 49
    "NO" : 11
    "?" : 20 

Below is a list of non-applicable rules:

NumberLevelName
R1ReinforcedChoosing and configuring your equipment
R2IntermediateConfigure BIOS/UEFI
R3IntermediateEnable UEFI secure boot
R4HighReplace preloaded keys
R28IntermediateStandard partitioning
R64ReinforcedConfiguring service privileges
R65ReinforcedPartitioning services
R66HighHardening of partitioning components
R76HighSealing and verifying file integrity
R77HighProtecting the seal database
R78ReinforcedEnclosing network services

Testing platform

%%{
    init: {
        "theme": "base",
        "themeVariables": {
            "pie1": "#674ea7",
            "pie2": "#ff00ff",
            "pie3": "#d9d9d9"
        }
    }
}%%
pie title Testing platform distribution
    "Docker" : 6
    "VM" : 2
    "?" : 61

Coverage

%%{
    init: {
        "theme": "base",
        "themeVariables": {
            "pie1": "#4285f4",
            "pie2": "#ffff00",
            "pie3": "#00ff00"
        }
    }
}%%
pie title Repository coverage rate
    "TODO" : 60
    "IN PROGRESS" : 1
    "DONE" : 8

Files

You will find all the files containing the data presented.

Progress files
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.

Chapter 3

3. Deploy Guide

Here you will find the steps for installing AutomA on your information system. Before explaining these steps, we would like to present a few aspects of AutomA that have an impact on its installation. AutomA is :

  • Offline: AutomA is designed to operate without an Internet connection, so it is possible to deploy our software on all your information systems.
  • Agentless: AutomA uses Ansible to run, so no agents are installed on your machines. All you need is your administration machine.
  • Exportable: AutomA allows you to export your configurations for application on other information systems. For example, AutomA can be installed on a machine with Internet access to take advantage of the latest updates while deploying configurations on other information systems.

Manual Installation

Step 1 - Prerequisites

The list of software required to install AutomA :

  • git
  • python3 (>3.9)
  • pip3
  • an up-to-date web browser

Step 2 - Downloading the repository

Using git, clone the repository:

git clone https://github.com/Autom-A/AutomA-WebUI.git

Once this has been done, you need to download the playbooks:

cd AutomA-WebUI
git submodule update --init --recursive 

Step 3 - Downloading dependencies

The project needs some python3 dependencies:

  • ansible-runner
  • Flask
  • flask-core
  • jinja2
  • pyyaml

To download them, run the following command:

pip3 install -r requirements.txt

Etape 4 - Run the project

Simply run the following command:

python3 src/main.py

Then open your navigator and type in address bar http://localhost:9123 (with default config).