0 EC2 SSH access: per-user keys and least privilege - kevwells.com

EC2 SSH access: per-user keys and least privilege

Short version: Prefer SSM Session Manager for human access. If you must use SSH, use per-user keys, a bastion or tight source CIDRs, and a hardened sshd_config. No shared logins. No password auth.

1) Choose the access pattern

Pattern When to use Notes
SSM Session Manager Default for most estates No SSH ports open; IAM-controlled; full session logs. Requires SSM Agent + instance profile.
SSH via bastion When SSM isn’t viable Security Group allows SSH from bastion only. Audit on the bastion; rotate keys.
EC2 Instance Connect Occasional break-glass Ephemeral keys via IAM; still needs 22/TCP exposure to trusted sources.

2) Prefer SSM: minimal setup

  1. Launch with the SSM Agent AMI (most Amazon Linux/Ubuntu images already include it).
  2. Attach an instance profile with the managed policy AmazonSSMManagedInstanceCore.
  3. Block SSH on Security Groups (no 22/TCP from the internet). Use Session Manager to connect.

Bonus: stream session logs to CloudWatch/S3 for audit. Set explicit log retention.

3) If using SSH: per-user keys and tight SGs

Security Group (example)

# Allow SSH only from bastion (10.0.10.5) and office CIDR
Inbound: 22/TCP  Source: 10.0.10.5/32, 203.0.113.0/24
Outbound: restrict as per policy (don't leave 0.0.0.0/0 if you can avoid it)

Create an admin user (cloud-init)

At launch, inject a minimal cloud-init to create a named user with your public key:

#cloud-config
users:
  - name: kevin
    gecos: Kevin Wells
    groups: sudo
    shell: /bin/bash
    sudo: "ALL=(ALL) NOPASSWD:ALL"
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... your_key_comment
packages:
  - fail2ban
runcmd:
  - sed -i 's/^#*PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
  - sed -i 's/^#*PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config
  - systemctl restart ssh || systemctl restart sshd

4) Harden sshd_config (baseline)

# /etc/ssh/sshd_config (add/ensure these)
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
LoginGraceTime 30
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowTcpForwarding no
X11Forwarding no
AllowAgentForwarding no
# Optional: limit which users can SSH
# AllowUsers kevin opsuser

Reload SSH safely: sshd -t then systemctl reload ssh (or sshd on some distros).

5) Bastion pattern (recommended over exposing 22 on every host)

  1. Lock inbound SSH on app/DB instances to the bastion’s private IP only.
  2. Harden the bastion: 2FA on admin workstations, fail2ban, audit, no agent forwarding.
  3. Jump through the bastion explicitly:
    ssh -J admin@bastion.example.com admin@10.0.20.17

6) IMDSv2 + Instance Profile (no static keys)

  • Require IMDSv2 on all EC2 instances; disable/limit hop count.
  • Attach an instance profile for apps to assume roles; never bake long-lived AWS keys onto servers.

7) Logging and review

  • Ship /var/log/auth.log (Debian/Ubuntu) or /var/log/secure (RHEL) to CloudWatch Logs.
  • Alarm on bursts of failed logins to sshd, and on any successful login from unexpected IP ranges.
  • Quarterly: rotate user keys; disable accounts not used in 90 days.

8) Break-glass access (document it)

  • Who can enable SSH on a locked-down host; for how long; and how this is recorded.
  • Rollback steps: remove temporary SG rules, rotate temporary keys, and confirm logs captured access.

Need a reviewed EC2 access pattern (SSM first, SSH where needed)? Request a call.

Security gaps in Linux and cloud systems risk downtime, data compromise, lost business — and compliance failures.

With 20+ years’ experience and active UK Security Check (SC) clearance, I harden Linux and cloud platforms for government, corporate, and academic sectors — ensuring secure, compliant, and resilient infrastructure.