updated initialization steps - added ansible role audit

This commit is contained in:
Mike Kell 2025-04-21 17:07:56 +00:00
parent e1e3eacbed
commit 06849a5927
8 changed files with 277 additions and 0 deletions

View File

@ -0,0 +1,39 @@
# 🛫 Preflight Role
This Ansible role performs pre-deployment checks to ensure that all required configuration values and infrastructure prerequisites are present and valid before continuing with the OpenCMMC Stack deployment.
## ✅ Features
- Validates required variables are defined
- Ensures SSH public key format is correct
- Checks email formatting using regex
- Verifies DNS resolution for target domain
- Logs all validation steps to the `evidence/99_preflight/` directory
## 📂 Evidence Artifacts
Validation checks are written to:
```
evidence/
└── 99_preflight/
└── validation_checks.md
```
## 🔍 Tags
Use with:
```bash
ansible-playbook site.yml --tags preflight
```
## 🔧 Variables Checked
- `default_user`
- `ssh_authorized_key`
- `domain_name`
- `hostname`
- `mailcow_admin_user`
- `mailcow_admin_password`
- `mailcow_fqdn`
- `mailcow_letsencrypt_email`

View File

@ -0,0 +1,2 @@
---
evidence_path: "../../evidence"

View File

@ -0,0 +1,10 @@
---
galaxy_info:
author: OpenCMMC Team
description: Validates required deployment inputs before running any roles
license: MIT
min_ansible_version: "2.10"
platforms:
- name: Ubuntu
versions:
- 22.04

View File

@ -0,0 +1,3 @@
---
- name: Run preflight variable validation
include_tasks: validate_vars.yml

View File

@ -0,0 +1,67 @@
---
- name: Assert required variables are set
assert:
that:
- default_user is defined
- ssh_authorized_key is defined
- domain_name is defined
- hostname is defined
- mailcow_admin_user is defined
- mailcow_admin_password is defined
- mailcow_fqdn is defined
fail_msg: "One or more required variables are missing. Check deployment_config.yml or group_vars/all.yml"
success_msg: "All required variables are present"
- name: Validate SSH public key format
assert:
that:
- ssh_authorized_key is match("^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp[0-9]+) [A-Za-z0-9+/=]+( .*)?$")
fail_msg: "SSH public key format is invalid"
success_msg: "SSH public key format appears valid"
- name: Validate email format
assert:
that:
- mailcow_letsencrypt_email is match("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")
fail_msg: "Mailcow Let's Encrypt email address is not valid"
success_msg: "Mailcow Let's Encrypt email format is valid"
- name: Check if domain_name resolves
command: "getent hosts {{ domain_name }}"
register: domain_lookup
ignore_errors: yes
- name: Assert domain_name resolves in DNS
assert:
that:
- domain_lookup.rc == 0
fail_msg: "Domain name {{ domain_name }} does not resolve. Verify DNS or /etc/hosts."
success_msg: "Domain name {{ domain_name }} resolves successfully"
- name: Log preflight validation results
copy:
dest: "{{ evidence_path }}/99_preflight/validation_checks.md"
content: |
# ✅ Preflight Validation Checks
**Status:** PASSED
Timestamp: {{ ansible_date_time.iso8601 }}
## Variables
- default_user: `{{ default_user }}`
- domain_name: `{{ domain_name }}`
- hostname: `{{ hostname }}`
- mailcow_admin_user: `{{ mailcow_admin_user }}`
- mailcow_fqdn: `{{ mailcow_fqdn }}`
- mailcow_letsencrypt_email: `{{ mailcow_letsencrypt_email }}`
## DNS Resolution
- Domain `{{ domain_name }}` resolved to: `{{ domain_lookup.stdout | default('N/A') }}`
## SSH Key Format
- SSH key validated against standard format
## Email Format
- Email passed regex validation
mode: '0644'

76
audit_roles.py Normal file
View File

@ -0,0 +1,76 @@
import os
from pathlib import Path
EXPECTED_ROLE_FILES = [
"tasks/main.yml",
"defaults/main.yml",
"meta/main.yml",
]
OPTIONAL_FILES = [
"handlers/main.yml",
"templates/",
"files/",
"vars/main.yml",
"molecule/default/converge.yml",
"molecule/default/verify.yml",
]
PLACEHOLDER_TERMS = ["TODO", "FILL_ME_IN", "REPLACE_ME"]
def is_placeholder(file_path):
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
return any(term in content for term in PLACEHOLDER_TERMS)
except Exception:
return False
def is_non_empty(file_path):
try:
return os.path.getsize(file_path) > 0
except Exception:
return False
def audit_role(role_path):
role_name = os.path.basename(role_path)
issues = []
for rel_path in EXPECTED_ROLE_FILES:
full_path = os.path.join(role_path, rel_path)
if not os.path.isfile(full_path):
issues.append(f"❌ MISSING: {rel_path}")
elif not is_non_empty(full_path):
issues.append(f"⚠️ EMPTY: {rel_path}")
elif is_placeholder(full_path):
issues.append(f"⚠️ PLACEHOLDER: {rel_path}")
for rel_path in OPTIONAL_FILES:
full_path = os.path.join(role_path, rel_path)
if os.path.exists(full_path) and os.path.isfile(full_path):
if not is_non_empty(full_path):
issues.append(f"⚠️ OPTIONAL EMPTY: {rel_path}")
elif is_placeholder(full_path):
issues.append(f"⚠️ OPTIONAL PLACEHOLDER: {rel_path}")
return role_name, issues
def main():
base_path = Path("ansible/roles/")
if not base_path.exists():
print("'ansible/roles/' directory not found.")
return
print("🔍 Auditing roles in:", base_path)
for role_dir in base_path.iterdir():
if role_dir.is_dir():
role_name, issues = audit_role(role_dir)
if issues:
print(f"\n🔎 Role: {role_name}")
for issue in issues:
print(" ", issue)
else:
print(f"✅ Role: {role_name} — All checks passed.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
import os
import yaml
import subprocess
CONFIG_PATH = "deployment/deployment_config.yml"
def prompt_input(prompt, default=None, required=True):
while True:
val = input(f"{prompt}{' [' + default + ']' if default else ''}: ").strip()
if val:
return val
elif default is not None:
return default
elif not required:
return ''
else:
print("This field is required.")
def write_config(config, path=CONFIG_PATH):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
yaml.dump(config, f)
print(f"\n✅ Config written to {path}")
def run_terraform():
print("\n🚀 Running Terraform provisioning...\n")
subprocess.run(["terraform", "init"], cwd="terraform")
subprocess.run(["terraform", "apply", "-auto-approve"], cwd="terraform")
def run_ansible():
print("\n🔧 Running Ansible deployment...\n")
subprocess.run([
"ansible-playbook", "-i", "inventory/terraform_inventory.yml", "site.yml"
])
def main():
print("🛡️ OpenCMMC Stack: Guided Deployment")
config = {
"cloud_provider": prompt_input("Cloud Provider (aws, azure, gcp, do, proxmox, bare-metal)"),
"fqdn": prompt_input("Root Fully Qualified Domain Name (e.g., open-cmmc.example.com)"),
"domain_name": prompt_input("Internal Domain Name (e.g., example.cmmc.local)"),
"admin_email": prompt_input("Administrator email (for certs and contact)"),
"admin_user": prompt_input("Admin system username", default="cmmcadmin"),
"ssh_pubkey_path": prompt_input("Path to SSH public key", default="~/.ssh/id_rsa.pub"),
"keycloak_realm": prompt_input("Keycloak Realm", default="OpenCMMC"),
"tailscale_key": prompt_input("Tailscale Auth Key", required=False),
}
write_config(config)
if prompt_input("Proceed with Terraform provisioning?", default="y").lower() == "y":
run_terraform()
if prompt_input("Proceed with Ansible deployment?", default="y").lower() == "y":
run_ansible()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,21 @@
# ✅ Preflight Validation Checks
**Status:** PASSED
Timestamp: {{ ansible_date_time.iso8601 }}
## Variables
- default_user: `{{ default_user }}`
- domain_name: `{{ domain_name }}`
- hostname: `{{ hostname }}`
- mailcow_admin_user: `{{ mailcow_admin_user }}`
- mailcow_fqdn: `{{ mailcow_fqdn }}`
- mailcow_letsencrypt_email: `{{ mailcow_letsencrypt_email }}`
## DNS Resolution
- Domain `{{ domain_name }}` resolved to: `{{ domain_lookup.stdout | default('N/A') }}`
## SSH Key Format
- SSH key validated against standard format
## Email Format
- Email passed regex validation