Projects

EC2 Linux Hardening — Defence in Depth

Layered hardening for RHEL on AWS EC2 — IAM least privilege, network scoping, host firewall, SSH lockdown, brute-force banning and monitoring, automated with Ansible.

RHEL · AWS EC2 · Ansible · firewalld · fail2ban · Python

Production

View repository →

Premise

A single control is a single point of failure. The question worth asking about any hardening measure is not “does this help?” but “what still gets through when this one fails?”

This build layers six independent controls across a RHEL instance on EC2, deliberately arranged so that defeating one does not grant access.

inbound attempt LAYER 1 · CLOUD IDENTITY IAM least privilege — root reserved for infrastructure creation only LAYER 2 · NETWORK PERIMETER Security Groups — explicit allow, Elastic IP for controlled access LAYER 3 · HOST FIREWALL firewalld — default-deny on the instance itself, independent of AWS LAYER 4 · AUTHENTICATION Key-based SSH only · password auth disabled · root login disabled LAYER 5 · ACTIVE RESPONSE fail2ban — automated banning after repeated failed attempts LAYER 6 · CLOUDWATCH ANSIBLE + PYTHON

Every layer is applied by automation, so the baseline is reproducible across any number of nodes.

Six independent controls. Monitoring observes every layer; Ansible applies every layer.

The layers

IAM least privilege. The root account is used for infrastructure creation and nothing else. Day-to-day operations run under scoped roles, so a compromised operational credential cannot restructure the account.

Security Groups. Explicit allow-listing at the AWS network boundary, with an Elastic IP giving a stable, controlled entry point.

firewalld on the host. A default-deny host firewall that does not depend on the cloud provider’s controls being correct. If a security group is misconfigured — the single most common cloud mistake — the instance still refuses the traffic.

SSH lockdown. Key-based authentication only, password authentication disabled, root login disabled. This removes credential guessing as an attack path entirely rather than trying to survive it.

fail2ban. Automated banning after repeated failures. With password auth already off this is defence in depth rather than the primary control — it absorbs the noise and reduces log volume from opportunistic scanning.

CloudWatch monitoring. Agent-based metrics and dashboards, so the controls above produce visible evidence rather than silent assumptions.

Applied state

Output of firewall-cmd --list-all showing the public zone active with a rich rule restricting SSH to a single source address.
Layer 3 — firewall-cmd --list-all. Default zone active, and a rich rule narrowing SSH to one source address rather than the whole internet.
fail2ban-client status sshd showing the sshd jail active with filter and ban actions configured.
Layer 5 — the sshd jail active. Zero bans here because password authentication is already disabled; fail2ban is depth, not the primary control.
CloudWatch alarms console showing an EC2 high CPU utilisation alarm in OK state with actions enabled.
Layer 6 — CloudWatch alarm with actions enabled, so the instance reports its own state rather than being assumed healthy.

The part that matters

All of it is applied by automation. Ansible playbooks and Python orchestration mean the baseline is a reproducible artifact, not a sequence of remembered commands. The same run applies to one node or several hundred, and two nodes provisioned the same way are provably identical.

Hardening applied by hand is not a baseline — it is a story about what someone did once. Hardening applied by code can be re-run, diffed, and proved.

Honest limitations

No benchmark measurement. The controls are sound but they were chosen by reasoning rather than scored against a published standard. Running the CIS Benchmark for RHEL through OpenSCAP would turn “hardened” into a number with a before and after — which is what an auditor or a client actually wants to see. That is the obvious next iteration.

No drift detection. The playbook establishes the baseline; nothing verifies it is still in place a month later.

Ingress only. Like most hardening work, this constrains what reaches the host and says nothing about what the host can reach outbound — which is where a compromise becomes an exfiltration.