initial comit with first sprint of terraform and ansible files

This commit is contained in:
Mike Kell 2025-04-18 00:47:11 +00:00
commit 6c9f41c26c
80 changed files with 1521 additions and 0 deletions

36
.editorconfig Normal file
View File

@ -0,0 +1,36 @@
# EditorConfig for OpenCMMC Stack
root = true
# Default settings for all files
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
# Python files
[*.py]
indent_size = 4
# YAML / Ansible / Terraform files
[*.yml]
indent_size = 2
[*.yaml]
indent_size = 2
[*.tf]
indent_size = 2
# Markdown files
[*.md]
trim_trailing_whitespace = false
indent_size = 2
# Shell scripts
[*.sh]
indent_size = 2
# Mermaid diagrams
[*.mmd]
indent_size = 2

34
.gitattributes vendored Normal file
View File

@ -0,0 +1,34 @@
# Normalize all text files to LF endings
* text=auto eol=lf
# Markdown, YAML, Terraform, and code files treated as text
*.md text
*.markdown text
*.yml text
*.yaml text
*.tf text
*.tfvars text
*.sh text
*.py text
*.mmd text
# Treat images and binary files as binary (no diff)
*.svg binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
*.zip binary
*.tar.gz binary
*.gz binary
# GitHub language statistics override (if needed)
docs/** linguist-documentation
*.md linguist-language=Markdown
*.mmd linguist-language=Mermaid
# Avoid diffs on lock and compiled files
*.lock binary
*.retry binary

301
.gitignore vendored Normal file
View File

@ -0,0 +1,301 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
*.retry
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# ————————————————————————
# MkDocs/Mermaid CLI Additions
# ————————————————————————
# Mermaid CLI output (SVGs or temp)
*.svg
*.mmdc-log
# Mermaid cache (if any future builds generate it)
.mermaid-cache/
# ————————————————————————
# Ansible Molecule Testing
# ————————————————————————
molecule/
**/molecule/*/.tox/
**/molecule/*/.molecule/
**/molecule/*/logs/
**/molecule/*/default/
# Lint and report files
*.retry
ansible-lint.log
molecule-report.xml
# ————————————————————————
# Ansible Role Packaging
# ————————————————————————
*.tar.gz
*.tar
*.zip
*.gz
# Built or temp roles
roles/**/collections/
roles/**/.python-version
roles/**/__pycache__/
roles/**/.pytest_cache/
# ————————————————————————
# SSH/Secrets Safety (add for clarity)
# ————————————————————————
# Never commit real keypairs or their fingerprints
id_rsa
id_rsa.pub
*.pem
*.crt
*.key
*.jks
# Example fallback (should be updated before use)
!example_id_rsa.pub
# ————————————————————————
# Terraform Local Logs and CLI Plan Files
# ————————————————————————
*.log
*.tfplan
# Explicitly ignore Terraform's crash diagnostic dirs
.terraform.d/
# ————————————————————————
# General Safety
# ————————————————————————
# Lock files generated by CI/test runs
.lockfile
open-cmmc.lock

0
README.md Normal file
View File

View File

@ -0,0 +1,39 @@
---
# 🔐 Global Access & Identity Settings
default_user: cmmcadmin
default_shell: /bin/bash
ssh_authorized_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
# 🌐 Network & Proxy Settings
nextcloud_port: 8080
mailcow_port: 443
keycloak_port: 8081
tailscale_auth_key: tskey-abc123
stepca_port: 9000
wazuh_port: 55000
# 📦 Container Images
nextcloud_aio_image: nextcloud/all-in-one:latest
keycloak_image: quay.io/keycloak/keycloak:24.0.2
# 🗂️ Data Directories
nextcloud_data_dir: /srv/nextcloud
backup_base_dir: /srv/backups
logs_dir: /var/log/open-cmmc
# ⚙️ System Users
svc_keycloak: svc_keycloak
svc_mailcow: svc_mailcow
svc_wazuh: svc_wazuh
svc_stepca: svc_stepca
# 🔄 Backup/Restore
restic_password: changeme-securely
restic_repo: /srv/backups/restic-repo
# 📛 DNS & Hostname
domain_name: example.cmmc.local
hostname: open-cmmc-gateway
# 📜 Default Realm for Keycloak
keycloak_realm: OpenCMMC

View File

@ -0,0 +1,30 @@
# file_storage Role
This Ansible role deploys **Nextcloud All-in-One (AIO)** as the secure file sharing and collaboration solution in the OpenCMMC Stack.
## Features
- Pulls and runs the official `nextcloud/all-in-one` container image
- Sets up a persistent storage directory for CUI/FCI content
- Configures container restart and port mapping for access via reverse proxy
## Variables
| Variable | Description | Default |
|-----------------------|--------------------------------------------|-----------------------------|
| `nextcloud_aio_image` | Container image for Nextcloud AIO | `nextcloud/all-in-one:latest` |
| `nextcloud_data_dir` | Host volume path for Nextcloud data | `/mnt/ncdata` |
| `nextcloud_port` | Port exposed on the host | `8080` |
## Example Playbook
```yaml
- hosts: all
roles:
- role: file_storage
```
## Notes
- Make sure this container is **behind a reverse proxy** (e.g., NGINX or Caddy).
- Configure DNS and TLS externally as needed.

View File

@ -0,0 +1,5 @@
---
nextcloud_aio_image: "nextcloud/all-in-one:latest"
nextcloud_container_name: "nextcloud-aio-mastercontainer"
nextcloud_data_dir: "/opt/nextcloud/data"
nextcloud_port: 8080

View File

@ -0,0 +1,2 @@
---
# Placeholder for handlers (e.g., restart Nextcloud container)

View File

@ -0,0 +1,9 @@
---
galaxy_info:
role_name: file_storage
author: open-cmmc
description: Deploy secure file collaboration using Nextcloud AIO
license: MIT
min_ansible_version: "2.10"
dependencies: []

View File

@ -0,0 +1,26 @@
---
- name: Create Nextcloud data directory
file:
path: "{{ nextcloud_data_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Pull Nextcloud AIO container image
containers.podman.podman_image:
name: "{{ nextcloud_aio_image }}"
- name: Deploy Nextcloud AIO container
containers.podman.podman_container:
name: "{{ nextcloud_container_name }}"
image: "{{ nextcloud_aio_image }}"
state: started
restart_policy: always
ports:
- "{{ nextcloud_port }}:8080"
volumes:
- "{{ nextcloud_data_dir }}:/mnt/ncdata:z"
- "nextcloud_aio_mastercontainer:/mnt/docker-aio-config:z"
env:
NEXTCLOUD_DATADIR: "/mnt/ncdata"

View File

@ -0,0 +1,3 @@
# Identity Role
This role installs and configures Keycloak for identity and access management, and Step-CA for internal certificate authority needs in the OpenCMMC Stack.

View File

@ -0,0 +1,6 @@
---
keycloak_version: "24.0.2"
keycloak_admin_user: "admin"
keycloak_admin_password: "securepassword"
keycloak_realm: "OpenCMMC"
step_ca_provisioner: "admin@example.com"

View File

@ -0,0 +1,12 @@
---
- name: Restart keycloak
ansible.builtin.systemd:
name: keycloak
state: restarted
enabled: true
- name: Restart step-ca
ansible.builtin.systemd:
name: step-ca
state: restarted
enabled: true

View File

@ -0,0 +1,12 @@
---
galaxy_info:
author: OpenCMMC Team
description: Identity and Access Management (IAM) role with Keycloak and Step-CA.
company: Kell Engineering
license: MIT
min_ansible_version: "2.12"
platforms:
- name: Ubuntu
versions:
- 22.04
dependencies: []

View File

@ -0,0 +1,35 @@
---
- name: Ensure Keycloak CLI (kcadm.sh) is installed
stat:
path: /opt/keycloak/bin/kcadm.sh
register: kcadm_path
- name: Install Keycloak CLI if missing
get_url:
url: https://downloads.jboss.org/keycloak/24.0.2/keycloak-24.0.2.zip
dest: /tmp/keycloak.zip
when: not kcadm_path.stat.exists
- name: Unarchive Keycloak CLI
unarchive:
src: /tmp/keycloak.zip
dest: /opt/
remote_src: yes
when: not kcadm_path.stat.exists
- name: Authenticate Keycloak admin CLI session
command: >
/opt/keycloak/bin/kcadm.sh config credentials --server http://localhost:8080/auth
--realm master --user {{ keycloak_admin_user }} --password {{ keycloak_admin_password }}
environment:
KCADM_CONFIG: /opt/keycloak/kcadm.config
- name: Create OpenCMMC realm
command: /opt/keycloak/bin/kcadm.sh create realms -s realm=OpenCMMC -s enabled=true
- name: Create groups
loop:
- Access_CUI
- Access_FCI
- Access_Proprietary
command: /opt/keycloak/bin/kcadm.sh create groups -r OpenCMMC -s name="{{ item }}"

View File

@ -0,0 +1,5 @@
---
- name: Configure Keycloak realm and groups
ansible.builtin.command:
cmd: "/opt/keycloak/bin/kcadm.sh create realms -s realm={{ keycloak_realm }} -s enabled=true"
when: keycloak_realm is defined

View File

@ -0,0 +1,33 @@
---
- name: Pull Keycloak image
containers.podman.podman_image:
name: quay.io/keycloak/keycloak:24.0.2
- name: Create systemd service user
user:
name: svc_keycloak
shell: /usr/sbin/nologin
system: yes
create_home: no
- name: Create Keycloak config directory
file:
path: /opt/services/keycloak
state: directory
owner: svc_keycloak
group: svc_keycloak
mode: '0755'
- name: Deploy Keycloak container
containers.podman.podman_container:
name: keycloak
image: quay.io/keycloak/keycloak:24.0.2
state: started
restart_policy: always
user: svc_keycloak
env:
KEYCLOAK_ADMIN: "{{ keycloak_admin_user }}"
KEYCLOAK_ADMIN_PASSWORD: "{{ keycloak_admin_password }}"
ports:
- "8080:8080"
command: "start --optimized"

View File

@ -0,0 +1,4 @@
---
- name: Enroll clients in Step-CA
ansible.builtin.debug:
msg: "Provision client certificates using Step-CA"

View File

@ -0,0 +1,4 @@
---
- name: Generate CA certs using step-ca CLI
ansible.builtin.debug:
msg: "Running step ca init with preconfigured values"

View File

@ -0,0 +1,12 @@
---
- name: Install Keycloak container
containers.podman.podman_container:
name: keycloak
image: quay.io/keycloak/keycloak:{{ keycloak_version }}
state: started
restart_policy: always
env:
KEYCLOAK_ADMIN: "{{ keycloak_admin_user }}"
KEYCLOAK_ADMIN_PASSWORD: "{{ keycloak_admin_password }}"
ports:
- "8080:8080"

View File

@ -0,0 +1,4 @@
---
- name: Setup OIDC clients for SSO integration
ansible.builtin.debug:
msg: "Configure OIDC clients for Mailcow, Gitea and SAML for Nextcloud"

View File

@ -0,0 +1,21 @@
---
- name: Include tasks to install and configure Keycloak
include_tasks: install_keycloak.yml
- name: Include tasks to configure realm and users
include_tasks: configure_realm.yml
- name: Include tasks to setup SSO integration
include_tasks: integrate_sso.yml
- name: Include tasks to configure MFA policies
include_tasks: setup_mfa.yml
- name: Include tasks to provision Step-CA
include_tasks: provision_step_ca.yml
- name: Include tasks to enroll clients in Step-CA
include_tasks: enroll_clients.yml
- name: Include tasks to generate CA certificates
include_tasks: generate_ca_certs.yml

View File

@ -0,0 +1,6 @@
---
- name: Install step-ca and create systemd service
ansible.builtin.copy:
src: systemd-step-ca.service.j2
dest: /etc/systemd/system/step-ca.service
notify: Restart step-ca

View File

@ -0,0 +1,4 @@
---
- name: Enable MFA for users
ansible.builtin.debug:
msg: "Enabling TOTP in Keycloak authentication flow"

View File

@ -0,0 +1,8 @@
---
- name: Install Tailscale
shell: curl -fsSL https://tailscale.com/install.sh | sh
args:
creates: /usr/bin/tailscale
- name: Bring up Tailscale interface
command: tailscale up --authkey {{ tailscale_authkey }}

View File

@ -0,0 +1,11 @@
[Unit]
Description=Keycloak Server
After=network.target
[Service]
ExecStart=/usr/bin/podman start -a keycloak
ExecStop=/usr/bin/podman stop -t 10 keycloak
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,7 @@
{
"address": ":9000",
"dnsNames": ["stepca.{{ inventory_hostname }}"],
"root": "step-ca-root.crt",
"crt": "step-ca-intermediate.crt",
"key": "step-ca-key.pem"
}

View File

@ -0,0 +1 @@
your-secure-password

View File

@ -0,0 +1,10 @@
[Unit]
Description=Step CA
After=network.target
[Service]
ExecStart=/usr/local/bin/step-ca /etc/step-ca/config/step-ca-config.json
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,6 @@
---
realm_clients:
- name: nextcloud
protocol: saml
- name: mailcow
protocol: oidc

View File

@ -0,0 +1,3 @@
# Ansible Role: keycloak_init
This role initializes a Keycloak instance with a predefined realm, groups, and clients for the OpenCMMC Stack.

View File

@ -0,0 +1,6 @@
---
- name: Register SAML client for Nextcloud
shell: '/opt/keycloak/bin/kcadm.sh create clients -r OpenCMMC -s clientId=nextcloud-aio -s protocol=saml -s enabled=true'
- name: Register OIDC client for Mailcow
shell: '/opt/keycloak/bin/kcadm.sh create clients -r OpenCMMC -s clientId=mailcow -s protocol=openid-connect -s enabled=true'

View File

@ -0,0 +1,9 @@
---
- name: Create Keycloak Groups
shell: '/opt/keycloak/bin/kcadm.sh create groups -r OpenCMMC -s name=Access_CUI'
- name: Create FCI Group
shell: '/opt/keycloak/bin/kcadm.sh create groups -r OpenCMMC -s name=Access_FCI'
- name: Create Proprietary Group
shell: '/opt/keycloak/bin/kcadm.sh create groups -r OpenCMMC -s name=Access_Proprietary'

View File

@ -0,0 +1,80 @@
---
- name: Check if Keycloak is already installed
stat:
path: /opt/keycloak/bin/kcadm.sh
register: keycloak_installed
- name: Download Keycloak if not already present
unarchive:
src: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_version }}/keycloak-{{ keycloak_version }}.tar.gz"
dest: /opt/
remote_src: yes
when: not keycloak_installed.stat.exists
- name: Rename keycloak directory
command: mv /opt/keycloak-{{ keycloak_version }} /opt/keycloak
args:
creates: /opt/keycloak/bin/kcadm.sh
when: not keycloak_installed.stat.exists
- name: Set executable permissions on kcadm.sh
file:
path: /opt/keycloak/bin/kcadm.sh
mode: '0755'
when: not keycloak_installed.stat.exists
- name: Log in to Keycloak Admin CLI
command: >
/opt/keycloak/bin/kcadm.sh config credentials
--server http://localhost:8080
--realm master
--user {{ keycloak_admin_user }}
--password {{ keycloak_admin_password }}
environment:
KC_HOME: /opt/keycloak
register: kcadm_login
changed_when: false
- name: Create OpenCMMC realm
command: >
/opt/keycloak/bin/kcadm.sh create realms
-s realm=OpenCMMC -s enabled=true
environment:
KC_HOME: /opt/keycloak
when: kcadm_login is succeeded
- name: Create groups
loop:
- Access_CUI
- Access_FCI
- Access_Proprietary
command: >
/opt/keycloak/bin/kcadm.sh create groups -r OpenCMMC -s name={{ item }}
environment:
KC_HOME: /opt/keycloak
when: kcadm_login is succeeded
- name: Create OIDC client for Mailcow
command: >
/opt/keycloak/bin/kcadm.sh create clients -r OpenCMMC
-s clientId=mailcow
-s enabled=true
-s protocol=openid-connect
-s publicClient=false
-s 'redirectUris=["https://mail.yourdomain.com/*"]'
environment:
KC_HOME: /opt/keycloak
when: kcadm_login is succeeded
- name: Create SAML client for Nextcloud
command: >
/opt/keycloak/bin/kcadm.sh create clients -r OpenCMMC
-s clientId=nextcloud
-s enabled=true
-s protocol=saml
-s 'redirectUris=["https://nextcloud.yourdomain.com/*"]'
-s 'attributes.saml.assertion.signature=true'
-s 'attributes.saml.force.post.binding=true'
environment:
KC_HOME: /opt/keycloak
when: kcadm_login is succeeded

View File

@ -0,0 +1,5 @@
---
- name: Create Keycloak Realm
shell: '/opt/keycloak/bin/kcadm.sh create realms -s realm=OpenCMMC -s enabled=true'
args:
creates: /opt/keycloak/data/realms/OpenCMMC

View File

@ -0,0 +1,10 @@
---
- name: Ensure kcadm.sh is present
stat:
path: /opt/keycloak/bin/kcadm.sh
register: kcadm_path
- name: Install unzip (required for kcadm)
apt:
name: unzip
state: present

View File

@ -0,0 +1,4 @@
---
wazuh_container_name: "wazuh-manager"
wazuh_image: "wazuh/wazuh:latest"
wazuh_config_path: "/opt/wazuh/config"

View File

@ -0,0 +1,2 @@
---
# Reserved for any future log restart triggers or alert changes

View File

@ -0,0 +1,9 @@
---
galaxy_info:
role_name: monitoring
author: open-cmmc
description: Centralized security logging and SIEM services using Wazuh and auditd
license: MIT
min_ansible_version: "2.10"
dependencies: []

View File

@ -0,0 +1,38 @@
---
- name: Pull Wazuh container image
containers.podman.podman_image:
name: wazuh/wazuh:latest
- name: Create Wazuh configuration directory
file:
path: "/opt/wazuh/config"
state: directory
owner: root
group: root
mode: "0755"
- name: Deploy Wazuh container
containers.podman.podman_container:
name: wazuh-manager
image: wazuh/wazuh:latest
state: started
restart_policy: always
ports:
- "1514:1514/udp"
- "1515:1515"
- "55000:55000"
volumes:
- "/opt/wazuh/config:/var/ossec/config:z"
- name: Ensure auditd is installed
apt:
name: auditd
state: present
update_cache: true
become: true
- name: Ensure auditd service is enabled and started
service:
name: auditd
state: started
enabled: true

View File

@ -0,0 +1,31 @@
# Podman Services Role
This Ansible role provisions and manages containerized infrastructure components (excluding Nextcloud AIO) using **Podman**. It includes configurations for Wazuh, Mailcow, Redis, and more.
## Variables
```yaml
podman_services:
- name: redis
image: redis:7
state: started
restart_policy: always
ports: []
volumes: []
env: {}
```
## Usage
Include in your playbook:
```yaml
- hosts: all
roles:
- role: podman_services
```
## Notes
- Only non-root Podman containers are supported
- Customize via `podman_services` variable in `defaults/main.yml`

View File

@ -0,0 +1,2 @@
---
podman_services: []

View File

@ -0,0 +1,2 @@
---
# Reserved for future restarts

View File

@ -0,0 +1,9 @@
---
galaxy_info:
role_name: podman_services
author: open-cmmc
description: Modular deployment of rootless Podman containers
license: MIT
min_ansible_version: "2.10"
dependencies: []

View File

@ -0,0 +1,38 @@
---
- name: Deploy keycloak container with Podman
containers.podman.podman_container:
name: keycloak
image: "{{ keycloak_image }}"
state: started
restart_policy: always
volumes:
- "{{ keycloak_data_dir }}:/data:z"
env:
CONFIG_PATH: "/data/config"
- name: Ensure systemd service is enabled for keycloak
copy:
dest: "/etc/systemd/system/podman-keycloak.service"
content: |
[Unit]
Description=Podman container for keycloak
Wants=network.target
After=network.target
[Service]
ExecStart=/usr/bin/podman start -a keycloak
ExecStop=/usr/bin/podman stop -t 10 keycloak
Restart=always
[Install]
WantedBy=multi-user.target
owner: root
group: root
mode: "0644"
- name: Reload systemd and enable service for keycloak
systemd:
daemon_reload: yes
name: podman-keycloak.service
enabled: yes
state: started

View File

@ -0,0 +1,38 @@
---
- name: Deploy mailcow container with Podman
containers.podman.podman_container:
name: mailcow
image: "{{ mailcow_image }}"
state: started
restart_policy: always
volumes:
- "{{ mailcow_data_dir }}:/data:z"
env:
CONFIG_PATH: "/data/config"
- name: Ensure systemd service is enabled for mailcow
copy:
dest: "/etc/systemd/system/podman-mailcow.service"
content: |
[Unit]
Description=Podman container for mailcow
Wants=network.target
After=network.target
[Service]
ExecStart=/usr/bin/podman start -a mailcow
ExecStop=/usr/bin/podman stop -t 10 mailcow
Restart=always
[Install]
WantedBy=multi-user.target
owner: root
group: root
mode: "0644"
- name: Reload systemd and enable service for mailcow
systemd:
daemon_reload: yes
name: podman-mailcow.service
enabled: yes
state: started

View File

@ -0,0 +1,12 @@
---
- name: Ensure Podman is installed
apt:
name: podman
state: present
become: true
- name: Pull and run Podman services
include_tasks: run_service.yml
loop: "{{ podman_services }}"
loop_control:
loop_var: service

View File

@ -0,0 +1,23 @@
---
- name: Pull image for {{ service.name }}
containers.podman.podman_image:
name: "{{ service.image }}"
- name: Create data directory for {{ service.name }}
file:
path: "{{ service.data_dir }}"
state: directory
owner: "{{ service.user | default('root') }}"
group: "{{ service.group | default('root') }}"
mode: "0755"
- name: Run {{ service.name }} container
containers.podman.podman_container:
name: "{{ service.name }}"
image: "{{ service.image }}"
state: started
restart_policy: always
ports: "{{ service.ports | default(omit) }}"
env: "{{ service.env | default({}) }}"
volumes: "{{ service.volumes | default([]) }}"
user: "{{ service.user | default(omit) }}"

View File

@ -0,0 +1,38 @@
---
- name: Deploy step_ca container with Podman
containers.podman.podman_container:
name: step_ca
image: "{{ step_ca_image }}"
state: started
restart_policy: always
volumes:
- "{{ step_ca_data_dir }}:/data:z"
env:
CONFIG_PATH: "/data/config"
- name: Ensure systemd service is enabled for step_ca
copy:
dest: "/etc/systemd/system/podman-step_ca.service"
content: |
[Unit]
Description=Podman container for step_ca
Wants=network.target
After=network.target
[Service]
ExecStart=/usr/bin/podman start -a step_ca
ExecStop=/usr/bin/podman stop -t 10 step_ca
Restart=always
[Install]
WantedBy=multi-user.target
owner: root
group: root
mode: "0644"
- name: Reload systemd and enable service for step_ca
systemd:
daemon_reload: yes
name: podman-step_ca.service
enabled: yes
state: started

View File

@ -0,0 +1,38 @@
---
- name: Deploy wazuh container with Podman
containers.podman.podman_container:
name: wazuh
image: "{{ wazuh_image }}"
state: started
restart_policy: always
volumes:
- "{{ wazuh_data_dir }}:/data:z"
env:
CONFIG_PATH: "/data/config"
- name: Ensure systemd service is enabled for wazuh
copy:
dest: "/etc/systemd/system/podman-wazuh.service"
content: |
[Unit]
Description=Podman container for wazuh
Wants=network.target
After=network.target
[Service]
ExecStart=/usr/bin/podman start -a wazuh
ExecStop=/usr/bin/podman stop -t 10 wazuh
Restart=always
[Install]
WantedBy=multi-user.target
owner: root
group: root
mode: "0644"
- name: Reload systemd and enable service for wazuh
systemd:
daemon_reload: yes
name: podman-wazuh.service
enabled: yes
state: started

View File

@ -0,0 +1,78 @@
# 🔐 Ansible Role: secure_ubuntu
Harden an Ubuntu 22.04 LTS host to meet **CMMC Level 2** compliance requirements using a modular, auditable Ansible role.
This role configures:
- SSH and login security
- Non-root administrative user
- System auditing and file integrity monitoring
- UFW firewall
- Secure banners for compliance
- Automatic updates and password policies
## ✅ CMMC Practices Addressed
| Domain | Practice | Description |
|--------|---------------|---------------------------------------------------------|
| AC | AC.1.001 | Limit system access to authorized users |
| AC | AC.3.017 | Display system use notifications (login banner) |
| CM | CM.2.062 | Employ security configuration baseline |
| SI | SI.1.210 | Identify unauthorized use of systems |
| SI | SI.3.219 | Detect and report unauthorized changes to software |
## 📦 Requirements
- Ubuntu 22.04 LTS
- Ansible >= 2.11
## 🚀 Role Variables
```yaml
secure_user: cmmcadmin
ssh_pubkey_path: "~/.ssh/id_rsa.pub"
```
> Set `ssh_pubkey_path` to the local path of the public key to be authorized for `secure_user`.
## 📁 Example Playbook
```yaml
- name: Apply CMMC hardening baseline
hosts: all
become: yes
roles:
- role: secure_ubuntu
vars:
secure_user: cmmcadmin
ssh_pubkey_path: "~/.ssh/id_rsa.pub"
```
## 📁 File Structure
```
roles/
└── secure_ubuntu/
├── defaults/
│ └── main.yml
├── meta/
│ └── main.yml
├── tasks/
│ ├── main.yml
│ ├── ssh.yml
│ ├── user.yml
│ ├── firewall.yml
│ ├── audit_aide.yml
│ ├── banner.yml
│ ├── updates.yml
│ └── password_policy.yml
└── README.md
```
## 🔒 License
MIT License
## 🧠 Author
Maintained by **Kell Engineering**
https://github.com/mtkell/open-cmmc-stack

View File

@ -0,0 +1,2 @@
---
admin_user: cmmcadmin

View File

@ -0,0 +1,5 @@
---
- name: Restart SSH
service:
name: ssh
state: restarted

View File

@ -0,0 +1,9 @@
---
galaxy_info:
role_name: secure_ubuntu
author: open-cmmc
description: Harden Ubuntu system for CMMC Level 2
license: MIT
min_ansible_version: "2.10"
dependencies: []

View File

@ -0,0 +1,12 @@
---
- name: Install audit and integrity tools
apt:
name:
- auditd
- aide
state: present
- name: Initialize AIDE database
command: aideinit
args:
creates: /var/lib/aide/aide.db.gz

View File

@ -0,0 +1,25 @@
---
- name: Deploy system login banner
template:
src: banner.txt.j2
dest: /etc/banner.txt
mode: '0644'
- name: Apply banner for SSH
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^Banner'
line: 'Banner /etc/banner.txt'
notify: Restart SSH
- name: Apply banner for console (TTY)
copy:
src: /etc/banner.txt
dest: /etc/issue
mode: '0644'
- name: Apply banner for pre-login
copy:
src: /etc/banner.txt
dest: /etc/issue.net
mode: '0644'

View File

@ -0,0 +1,16 @@
---
- name: Install UFW firewall
apt:
name: ufw
state: present
- name: Set UFW default deny policy
ufw:
state: enabled
policy: deny
- name: Allow SSH through firewall
ufw:
rule: allow
port: "22"
proto: tcp

View File

@ -0,0 +1,15 @@
---
- name: Include SSH hardening tasks
import_tasks: ssh.yml
- name: Include firewall configuration
import_tasks: firewall.yml
- name: Include audit logging setup
import_tasks: audit.yml
- name: Include banner setup
import_tasks: banners.yml
- name: Include automatic update configuration
import_tasks: updates.yml

View File

@ -0,0 +1,12 @@
---
- name: Set password complexity
lineinfile:
path: /etc/security/pwquality.conf
regexp: '^minlen'
line: 'minlen = 14'
- name: Lock out after 5 failed login attempts
lineinfile:
path: /etc/pam.d/common-auth
line: 'auth required pam_tally2.so deny=5 onerr=fail unlock_time=900'
create: yes

View File

@ -0,0 +1,27 @@
---
- name: Disable root login over SSH
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
notify: Restart SSH
- name: Disable password authentication
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PasswordAuthentication'
line: 'PasswordAuthentication no'
notify: Restart SSH
- name: Ensure non-root admin user exists
user:
name: cmmcadmin
shell: /bin/bash
groups: sudo
create_home: yes
state: present
- name: Add authorized SSH key for cmmcadmin
authorized_key:
user: cmmcadmin
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

View File

@ -0,0 +1,12 @@
---
- name: Install unattended-upgrades
apt:
name: unattended-upgrades
state: present
- name: Enable automatic security updates
copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

View File

@ -0,0 +1,13 @@
---
- name: Ensure non-root user exists
user:
name: cmmcadmin
shell: /bin/bash
groups: sudo
state: present
create_home: yes
- name: Add authorized SSH key for cmmcadmin
authorized_key:
user: cmmcadmin
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

View File

@ -0,0 +1,10 @@
*** WARNING ***
This computer system is the property of {{ organization_name | default('Your Organization') }}. It is for authorized use only.
By accessing this system, you acknowledge and consent to the following:
- All information is subject to monitoring by authorized personnel.
- Unauthorized use may result in disciplinary action, civil penalties, or criminal prosecution.
- You agree to comply with all security and data handling policies.
This system is protected under CMMC Level 2 compliance controls.

View File

@ -0,0 +1,13 @@
---
- name: Test secure_ubuntu role on localhost
hosts: localhost
become: yes
gather_facts: true
vars:
secure_user: testadmin
ssh_pubkey_path: "~/.ssh/id_rsa.pub"
ssh_port: 22
roles:
- role: secure_ubuntu

View File

@ -0,0 +1,6 @@
---
- name: Harden Ubuntu for CMMC Stack
hosts: localhost
become: yes
roles:
- secure_ubuntu

10
ansible/site.yml Normal file
View File

@ -0,0 +1,10 @@
---
- name: Apply baseline security and deploy OpenCMMC Stack
hosts: all
become: true
roles:
- role: secure_ubuntu
- role: identity
- role: podman_services
- role: file_storage
- role: monitoring

View File

@ -0,0 +1,15 @@
# 📌 Evidence Scoping Documentation
## Purpose
This section documents the scoping boundaries for the OpenCMMC Stack environment. It includes diagrams, system inventory, and asset classification relevant to CMMC Level 2 compliance.
## Included Artifacts
- Network/system diagrams
- Inventory list
- Boundary identification
- Cloud vs. on-prem segregation
## Review Checklist
- [ ] Scope document reviewed by compliance officer
- [ ] Asset list includes all CUI-processing components
- [ ] Diagrams show trust boundaries and segmentation

View File

@ -0,0 +1,15 @@
# 🔐 Evidence Identity and Access Management
## Purpose
This folder contains evidence showing how user accounts, roles, MFA, and authentication systems are managed via Keycloak and Tailscale.
## Included Artifacts
- Realm export (`keycloak-realm-export.json`)
- Screenshots of MFA policy
- Group-to-role mapping export
- Tailscale ACL and device log
## Review Checklist
- [ ] MFA enforced for all privileged users
- [ ] User roles mapped and validated
- [ ] Keycloak policies match SSP configuration

View File

@ -0,0 +1,15 @@
# 📋 Evidence Ansible Execution Logs
## Description
This directory contains raw output of Ansible playbooks applied to the Ubuntu host.
## Artifacts
- `secure_ubuntu_run.log`
- `ansible-playbook-timestamped.json`
- Output showing configuration and user enforcement
## Reviewer Notes
Ensure playbook includes:
- SSH key enforcement
- Root login disabled
- Required auditd and aide tasks completed

View File

@ -0,0 +1,15 @@
# 🛡️ Evidence System Hardening
## Purpose
Evidence of host OS and container baseline security including hardened configurations, SSH controls, and auditd/aide logs.
## Included Artifacts
- Ansible playbook logs
- Auditd rules and logs
- SSH daemon configs
- AIDE database init and scan result
## Review Checklist
- [ ] Ansible logs show successful hardening
- [ ] Login banners and password policies verified
- [ ] Audit and integrity scans scheduled

View File

@ -0,0 +1,15 @@
# 📁 Evidence File Sharing & Collaboration
## Purpose
Evidence of Nextcloud AIO configuration for secure file sharing and internal team access controls.
## Included Artifacts
- SAML config screenshots
- File Access Control rules
- Team Folder setup for CUI/FCI
- Antivirus scanner logs
## Review Checklist
- [ ] Access control groups defined
- [ ] External sharing is blocked for CUI
- [ ] Antivirus scans for uploads are active

View File

@ -0,0 +1,15 @@
# ✉️ Evidence Secure Email Configuration
## Purpose
Evidence related to the secure deployment and configuration of Mailcow for sending and receiving secure communications.
## Included Artifacts
- TLS/DKIM/SPF reports
- SSO integration with Keycloak
- Mail logs for delivery success and rejection
- Email relay and filter rules
## Review Checklist
- [ ] DKIM keys configured and tested
- [ ] TLS enforced on all SMTP/IMAP traffic
- [ ] Mailcow access restricted via Tailscale ACLs

View File

@ -0,0 +1,15 @@
# 📡 Evidence Monitoring & SIEM Integration
## Purpose
Evidence supporting logging, alerting, and forensic readiness using Wazuh and system-level audit mechanisms.
## Included Artifacts
- Wazuh agent and server logs
- SIEM dashboard screenshots
- Custom alert rules
- Log forwarding rules (if applicable)
## Review Checklist
- [ ] Agent deployment logs available
- [ ] Alerts fire on auth failures, sudo, etc.
- [ ] Central log retention meets policy

View File

@ -0,0 +1,15 @@
# 💾 Evidence Backups & Recovery Testing
## Purpose
Documentation of backup routines, tools (Restic/Borg), encrypted vaults, and restore verification processes.
## Included Artifacts
- Restic/Borg configuration files
- Restore logs and diffs
- Encryption key storage policy
- Backup rotation reports
## Review Checklist
- [ ] Encryption is enabled at rest
- [ ] Monthly restore test logs present
- [ ] Offsite backup location validated

View File

@ -0,0 +1,15 @@
# 📜 Evidence Policies and Administrative Controls
## Purpose
This section contains administrative control evidence including signed policies, role assignments, and procedure documents.
## Included Artifacts
- Access control policy (AC-1)
- Incident response plan
- User onboarding/offboarding procedures
- Policy acceptance logs
## Review Checklist
- [ ] Policies approved and version controlled
- [ ] All users have acknowledged relevant policies
- [ ] Procedures align with technical implementation

8
terraform/bootstrap.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
apt update && apt install -y git python3-pip curl ufw
pip3 install ansible
ufw allow OpenSSH
ufw --force enable
git clone https://github.com/mtkell/open-cmmc-stack.git /opt/open-cmmc-stack
cd /opt/open-cmmc-stack/ansible
ansible-playbook -i localhost, secure_ubuntu.yml

18
terraform/main.tf Normal file
View File

@ -0,0 +1,18 @@
provider "digitalocean" {
token = var.do_token
}
resource "digitalocean_droplet" "secure_host" {
name = "cmmc-hardened"
region = "nyc3"
size = "s-2vcpu-4gb"
image = "ubuntu-22-04-x64"
ssh_keys = [var.ssh_fingerprint]
user_data = file("${path.module}/bootstrap.sh")
tags = ["cmmc", "secure-host"]
}
output "droplet_ip" {
value = digitalocean_droplet.secure_host.ipv4_address
}

View File

@ -0,0 +1,2 @@
do_token = "your_digitalocean_token_here"
ssh_fingerprint = "your_local_ssh_key_fingerprint"

10
terraform/variables.tf Normal file
View File

@ -0,0 +1,10 @@
variable "do_token" {
description = "DigitalOcean API token"
type = string
sensitive = true
}
variable "ssh_fingerprint" {
description = "Your public SSH key fingerprint registered with DigitalOcean"
type = string
}