A production-grade Infrastructure as Code (IaC) portfolio project demonstrating advanced Linux Systems Administration, Security Hardening, and DevOps automation.
This repository contains a complete Ansible-driven lab environment designed to mirror a real-world R&D SysAdmin setup. It automates the provisioning of developer workstations, CI/CD runners, and core infrastructure services, emphasizing security best practices (CIS-lite) and disaster recovery.
GitHub Repository: https://github.com/daryllundy/field-lab-ansible-portfolio
Key Features:
- Automated Hardening: CIS-lite baseline, SSH hardening, UFW firewall, and Fail2Ban.
- Developer Platform: Automated deployment of VS Code Server and JupyterLab for R&D workflows.
- CI/CD Infrastructure: Self-hosted GitLab CE and Docker-based GitLab Runners.
- Resilient Storage: NFS-backed storage with automated Restic backups and DR restore playbooks.
- Observability: EDR-style telemetry using Elastic Agent.
| Category | Technologies & Skills |
|---|---|
| Automation & IaC | Ansible (Roles, Playbooks, Molecule Testing), Makefiles, Bash Scripting |
| Linux Administration | Ubuntu/Debian, Systemd, User Management, Package Management (Apt) |
| Security | CIS Hardening, SSH Key Management, Firewall (UFW), Fail2Ban, Unattended Upgrades |
| Networking | VLANs, DNS/DHCP (Dnsmasq), Unbound, Netplan |
| DevOps & CI/CD | GitLab CI, GitLab Runners, Docker, GitHub Actions |
| Storage & DR | NFS, Restic Backups, Disaster Recovery Planning & Testing |
graph TD
subgraph "VLAN 40: Infrastructure"
GitLab[GitLab CE]
NFS[NFS Server]
DNS[DNS/DHCP]
Wazuh[Wazuh/Elastic]
end
subgraph "VLAN 50: Workstations"
WS1[Ubuntu Workstation 1]
WS2[Ubuntu Workstation 2]
end
subgraph "VLAN 60: Lab Nodes"
Pi1[Raspberry Pi 1]
Pi2[Raspberry Pi 2]
end
subgraph "VLAN 70: Runners"
Runner1[GitLab Runner 1]
Runner2[GitLab Runner 2]
end
WS1 --> GitLab
WS1 --> NFS
Runner1 --> GitLab
Pi1 --> DNS
WS1 --> DNS
- Python 3.11+
- Docker (for Molecule tests)
Initialize the project and install dependencies:
make setup
source .venv/bin/activateEdit inventories/lab.ini to match your target hosts. Update secrets in group_vars/.
Bootstrap and harden the environment:
make bootstrap
make hardenDeploy developer tools and infrastructure:
make dev-tools
make network
make storageRun the test suite (requires Docker):
make testβββ ansible/
β βββ playbooks/ # Main orchestration playbooks
β βββ roles/ # Reusable Ansible roles (hardening, gitlab, etc.)
β βββ files/ # Static assets
βββ inventories/ # Host definitions
βββ group_vars/ # Configuration variables
βββ docs/ # Operational runbooks
βββ diagrams/ # Architecture diagrams
βββ Makefile # Automation shortcuts
MIT License Β© 2025 Daryl Lundy
This project includes comprehensive testing to ensure infrastructure correctness and reliability.
The testing strategy combines multiple approaches:
| Test Type | Framework | Purpose | Count |
|---|---|---|---|
| Property-Based Tests | Hypothesis | Validate correctness across random inputs | 15 tests |
| Configuration Tests | pytest | Verify configuration structure | 16 tests |
| Integration Tests | Molecule + Testinfra | End-to-end role validation | 10 roles |
| Linting | ansible-lint, yamllint | Code quality and best practices | - |
# Run all tests (excluding Molecule)
make test
# Run linting only
make lint
# Run property-based tests
uv run pytest tests/test_property_*.py -v
# Run configuration validation tests
uv run pytest tests/ -v -k "not molecule"
# Run Molecule tests for a specific role
cd ansible/roles/base_hardening
molecule test1. Property-Based Tests (Hypothesis)
These tests validate correctness properties across 100 randomly generated inputs each:
# All property tests
uv run pytest tests/test_property_*.py -v
# Specific property test categories
uv run pytest tests/test_property_idempotency.py -v
uv run pytest tests/test_property_variable_substitution.py -v
uv run pytest tests/test_property_inventory_parsing.py -v2. Configuration Validation Tests
# Inventory and variable structure
uv run pytest tests/test_inventory_config.py -v
# CI/CD configuration
uv run pytest tests/test_cicd.py -v
# Makefile targets
uv run pytest tests/test_makefile.py -v
# Molecule configuration
uv run pytest tests/test_molecule_config.py -v3. Molecule Integration Tests
Molecule tests run roles in Docker containers and verify the results:
# Test all roles with Molecule
make test
# Test individual role
cd ansible/roles/<role_name>
molecule test
# Available roles with Molecule tests:
# - base_hardening
# - users
# - packages
# - gitlab
# - gitlab_runner
# - vscode_server
# - jupyter
# - network
# - storage
# - monitoring
# - dr_testProperty-based tests use Hypothesis to generate random test cases and validate universal properties:
- Property 1: Base hardening configuration consistency
- Property 2: Users role configuration consistency
- Property 3: Configuration state consistency
# Example: Validates that configuration survives serialization
@given(config=base_hardening_config())
def test_base_hardening_idempotency(config):
yaml_str = yaml.dump(config)
reloaded = yaml.safe_load(yaml_str)
assert reloaded == config- Property 4: Global variables round-trip validation
- Property 5: Workstation variables round-trip validation
- Property 6: Runner variables round-trip validation
- Property 7: Infrastructure variables round-trip validation
- Property 8: URL substitution consistency
- Property 9: Path substitution consistency
- Property 10: Inventory parsing consistency
- Property 11: Group membership validation
- Property 12: Host resolution validation
- Property 13: Inventory round-trip idempotency
- Property 14: Group hierarchy validation
- Property 15: Host update consistency
# View test coverage summary
uv run pytest tests/ --cov=. --cov-report=term-missing
# Generate HTML coverage report
uv run pytest tests/ --cov=. --cov-report=htmlGitHub Actions automatically runs on every push and pull request:
# .github/workflows/ci.yml
- Linting (ansible-lint, yamllint)
- Python 3.11 environment
- Automated validation of all Ansible code- Create test file in
tests/test_property_*.py - Use Hypothesis strategies to generate test data
- Tag with
@pytest.mark.property - Run 100+ iterations per test
from hypothesis import given, strategies as st, settings
@pytest.mark.property
@given(value=st.text())
@settings(max_examples=100)
def test_my_property(value):
# Test implementation
assert some_property_holds(value)- Create
molecule/default/directory in role - Add
molecule.ymlconfiguration - Create
converge.ymlplaybook - Add testinfra tests in
tests/test_default.py
cd ansible/roles/my_role
molecule init scenario
molecule testDocker Issues (Molecule)
# Ensure Docker is running
docker ps
# Clean up old containers
molecule destroyHypothesis Failures
# View detailed failure information
uv run pytest tests/test_property_*.py -v --tb=long
# Reproduce specific failure
# Hypothesis saves failing examples in .hypothesis/Import Errors
# Reinstall dependencies
uv sync
# Verify installation
uv run pytest --version- Total Tests: 31+ (excluding Molecule)
- Property Tests: 15 tests Γ 100 iterations = 1,500 test cases
- Configuration Tests: 16 tests
- Molecule Integration Tests: 10 roles
- Average Test Runtime: ~2 seconds (property tests), ~5 minutes (Molecule per role)