initial
This commit is contained in:
commit
b00d69e101
8 changed files with 242 additions and 0 deletions
48
.forgejo/workflows/deploy.yml
Normal file
48
.forgejo/workflows/deploy.yml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
name: Validate and deploy
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
validate-and-deploy:
|
||||
runs-on: docker
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
||||
- name: Scan secrets
|
||||
run: gitleaks detect --source . --redact --no-banner
|
||||
|
||||
- name: Check Terraform formatting
|
||||
run: terraform fmt -check -recursive
|
||||
|
||||
- name: Initialize Terraform
|
||||
run: terraform init -backend=false -input=false
|
||||
|
||||
- name: Validate Terraform
|
||||
run: terraform validate
|
||||
|
||||
- name: Plan Terraform
|
||||
run: terraform plan -input=false -no-color -out=tfplan
|
||||
|
||||
- name: Lint Ansible
|
||||
run: ansible-lint deploy/site.yml
|
||||
|
||||
- name: Deploy
|
||||
if: ${{ forgejo.event.action == 'closed' && forgejo.event.pull_request.merged == true }}
|
||||
env:
|
||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||
run: bash deploy/deploy.sh
|
||||
23
NOTES.md
Normal file
23
NOTES.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
* Changes to terraform/ansible*
|
||||
I noticed an extra set of brackets that VS Code didn't flag. Running a quick terraform validate catches small syntax issues like this before they cause deployment errors.
|
||||
|
||||
In main.tf, I scoped cidr_blocks down from 0.0.0.0/0 to a sample subnet (180.191.0.0/16). While still somewhat broad to accommodate dynamic ISP leases that reset every 48 hours, it significantly reduces the initial attack surface.
|
||||
|
||||
Alternative Access & Security Models
|
||||
Rather than leaving SSH exposed, the best practice—even for small instances—is to remove port 22 from the security group entirely. A few options to consider:
|
||||
|
||||
Tailscale / Headscale (Recommended): Uses Tailscale SSH to manage access securely. Users authenticate via Identity Providers (IdPs) like Google or GitHub, while Headscale handles key rotations, ACLs, and permissions. I personally use this setup in production for small cloud workloads; it provides excellent ACL control and a clean management interface.
|
||||
|
||||
AWS SSM Session Manager: Allows secure CLI and console access via the AWS agent without open inbound ports, though executing local tools like Ansible internally requires additional setup.
|
||||
|
||||
Cloudflare Tunnels: Useful for routing developer traffic directly to the instance without exposing public IPs, especially in environments requiring DNS over tunnels.
|
||||
|
||||
I prefer the Tailscale/Headscale combination as a modern, manageable solution that keeps port 22 completely closed to the internet.
|
||||
|
||||
Handling Port 80 Ingress
|
||||
I added port 80 to the ingress rules to support standard HTTP traffic. While this opens a public surface area, network risks can easily be mitigated at the Linux OS level (e.g., hardening TCP kernel parameters against SYN floods). In practice, Nginx will use port 80 primarily to issue 301 permanent redirects to HTTPS (port 443).
|
||||
|
||||
|
||||
*Scenario Response: TinyCommerce App Intermittent Health Check Failures*
|
||||
Part 3 — Incident note (~30 min, written)
|
||||
A Scenario response for the TinyCommerce app with intermittent health check failures. Detection relies on baseline health check monitoring. The issue would likely present as a 5xx HTTP error specifically a 502 Bad Gateway indicating an application-level problem within the TinyCommerce app. Assuming the application has a reliable deployment pipeline, I would start by investigating the error logs. For example, if the app crashes whenever it attempts a file write, I would check system monitoring and host logs for clear indicators like disk space exhaustion. On small instances, unrotated application logs, Docker container logs, or bloated volumes are almost always the root cause of disk exhaustion and subsequent crashes.
|
||||
8
deploy/deploy.sh
Normal file
8
deploy/deploy.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
terraform apply -input=false -auto-approve tfplan
|
||||
ansible-playbook -i inventory deploy/site.yml
|
||||
echo "Deployed."
|
||||
34
deploy/site.yml
Normal file
34
deploy/site.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
- name: Deploy TinyCommerce
|
||||
hosts: app
|
||||
become: true
|
||||
vars_files:
|
||||
- vars/vault.yml
|
||||
|
||||
tasks:
|
||||
- name: Install nginx
|
||||
ansible.builtin.apt:
|
||||
name: nginx
|
||||
state: present
|
||||
|
||||
- name: Write app config
|
||||
ansible.builtin.template:
|
||||
src: app.env.j2
|
||||
dest: /etc/tinycommerce/app.env
|
||||
mode: "0600"
|
||||
|
||||
- name: Write nginx config
|
||||
ansible.builtin.template:
|
||||
src: nginx_web1.conf.j2
|
||||
dest: /etc/nginx/sites-enabled/nginx_web1.conf
|
||||
mode: "0644"
|
||||
|
||||
- name: Restart nginx
|
||||
ansible.builtin.service:
|
||||
name: nginx
|
||||
state: restarted
|
||||
|
||||
- name: Restart app service
|
||||
ansible.builtin.service:
|
||||
name: tinycommerce
|
||||
state: restarted
|
||||
1
deploy/templates/app.env.j2
Normal file
1
deploy/templates/app.env.j2
Normal file
|
|
@ -0,0 +1 @@
|
|||
DB_PASSWORD={{ db_password }}
|
||||
80
deploy/templates/nginx_web1.conf.j2
Normal file
80
deploy/templates/nginx_web1.conf.j2
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
server_name www.tinycommerce.com;
|
||||
set $maintenance_site_key "tinycommerce.com";
|
||||
set $base /var/www/html/tinycommerce.com;
|
||||
root /var/www/html/tinycommerce.com/public;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/tinycommerce.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/tinycommerce.com/privkey.pem;
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/tinycommerce.com/chain.pem;
|
||||
|
||||
include snippets/cloudflare-realip.conf;
|
||||
include snippets/security.conf;
|
||||
include snippets/maintenance-mode.conf;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
index index.php;
|
||||
|
||||
location / {
|
||||
if (-f /etc/nginx/maintenance/enabled) {
|
||||
return 503;
|
||||
}
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
include snippets/general.conf;
|
||||
include snippets/wordpress.conf;
|
||||
client_max_body_size 0;
|
||||
|
||||
|
||||
location ~ \.php$ {
|
||||
if (-f /etc/nginx/maintenance/enabled) {
|
||||
return 503;
|
||||
}
|
||||
fastcgi_pass unix:/var/run/php/php-wordpress.sock;
|
||||
include snippets/php-wordpress-fastcgi.conf;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
server_name tinycommerce.com;
|
||||
set $maintenance_site_key "tinycommerce.com";
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/tinycommerce.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/tinycommerce.com/privkey.pem;
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/tinycommerce.com/chain.pem;
|
||||
include snippets/cloudflare-realip.conf;
|
||||
include snippets/maintenance-mode.conf;
|
||||
|
||||
location / {
|
||||
if (-f /etc/nginx/maintenance/enabled) {
|
||||
return 503;
|
||||
}
|
||||
return 301 https://www.tinycommerce.com$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name tinycommerce.com www.tinycommerce.com;
|
||||
set $maintenance_site_key "tinycommerce.com";
|
||||
include snippets/letsencrypt.conf;
|
||||
include snippets/cloudflare-realip.conf;
|
||||
include snippets/maintenance-mode.conf;
|
||||
|
||||
location / {
|
||||
if (-f /etc/nginx/maintenance/enabled) {
|
||||
return 503;
|
||||
}
|
||||
return 301 https://www.tinycommerce.com$request_uri;
|
||||
}
|
||||
}
|
||||
2
deploy/vars/vault.yml
Normal file
2
deploy/vars/vault.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
db_password: "{{ lookup('ansible.builtin.env', 'DB_PASSWORD') }}"
|
||||
46
main.tf
Normal file
46
main.tf
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
resource "aws_security_group" "app" {
|
||||
name = "tinycommerce-app-sg"
|
||||
description = "Allow app and SSH traffic"
|
||||
|
||||
ingress {
|
||||
from_port = 22
|
||||
to_port = 22
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["180.191.0.0/16"]
|
||||
}
|
||||
|
||||
ingress {
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
ingress {
|
||||
from_port = 80
|
||||
to_port = 80
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_instance" "app" {
|
||||
ami = "ami-0abcdef1234567890"
|
||||
instance_type = "t3.small"
|
||||
security_groups = [aws_security_group.app.name]
|
||||
|
||||
tags = {
|
||||
Name = "tinycommerce-app-01"
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue