updated initialization steps - added ansible role audit
This commit is contained in:
parent
e1e3eacbed
commit
06849a5927
|
|
@ -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`
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
evidence_path: "../../evidence"
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
- name: Run preflight variable validation
|
||||
include_tasks: validate_vars.yml
|
||||
|
|
@ -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'
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue