From 8a9e53a77d38dea88ca88562dfc991a075fd4027 Mon Sep 17 00:00:00 2001 From: yeounhyeok Date: Thu, 4 Jun 2026 18:19:18 +0900 Subject: [PATCH 1/7] =?UTF-8?q?chore:=20Cloudflare=20Tunnel=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ansible/README.md | 38 ++++++++++++----- ansible/playbooks/site.yml | 2 + ansible/roles/cloudflared/defaults/main.yml | 9 ++++ ansible/roles/cloudflared/tasks/main.yml | 41 +++++++++++++++++++ .../cloudflared/templates/compose.yml.j2 | 6 +++ 5 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 ansible/roles/cloudflared/defaults/main.yml create mode 100644 ansible/roles/cloudflared/tasks/main.yml create mode 100644 ansible/roles/cloudflared/templates/compose.yml.j2 diff --git a/ansible/README.md b/ansible/README.md index 76decf3..920776d 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -1,10 +1,28 @@ -# Ansible - -Ansible contains environment-specific inventories and reusable roles. - -## Structure - -- `inventories/dev`: development inventory and shared vars -- `playbooks/bootstrap.yml`: base server bootstrap -- `roles/common`: common baseline packages and configuration -- `roles/docker`: container runtime placeholder role +# Ansible + +Ansible contains environment-specific inventories and reusable roles. + +## Structure + +- `inventories/dev`: development inventory and shared vars +- `playbooks/bootstrap.yml`: base server bootstrap +- `roles/common`: common baseline packages and configuration +- `roles/docker`: container runtime placeholder role + +- `roles/cloudflared`: Cloudflare Tunnel container for reverse proxy ingress + +## Cloudflare Tunnel + +`cloudflared` is managed as a Docker Compose service on the app host. + +Required runtime secret: + +- `cloudflared_tunnel_token`: Cloudflare Tunnel token. Inject via Ansible Vault, inventory secrets, or `-e`; never commit the real value. + +Run only this role: + +```bash +ansible-playbook playbooks/site.yml --tags cloudflared -e "cloudflared_tunnel_token=..." +``` + +Cloudflare DNS/ingress is expected to point public hostnames, such as temporary `*.yeoun.org` records, at the Cloudflare Tunnel and then reverse proxy to the EC2 origin. diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml index aa1153f..f00e8e7 100644 --- a/ansible/playbooks/site.yml +++ b/ansible/playbooks/site.yml @@ -10,5 +10,7 @@ - role: awscli tags: [awscli] - docker + - role: cloudflared + tags: [cloudflared, tunnel] - role: postgres tags: [db, postgres] diff --git a/ansible/roles/cloudflared/defaults/main.yml b/ansible/roles/cloudflared/defaults/main.yml new file mode 100644 index 0000000..0d99527 --- /dev/null +++ b/ansible/roles/cloudflared/defaults/main.yml @@ -0,0 +1,9 @@ +--- +# Cloudflare Tunnel role defaults. Secrets must be injected from inventory, vault, or -e. +cloudflared_project_dir: /opt/moa/cloudflared +cloudflared_container_name: moa-cloudflared +cloudflared_image: cloudflare/cloudflared:latest +cloudflared_restart_policy: unless-stopped + +# Required secret at runtime. Do not commit the real token. +cloudflared_tunnel_token: "" diff --git a/ansible/roles/cloudflared/tasks/main.yml b/ansible/roles/cloudflared/tasks/main.yml new file mode 100644 index 0000000..677613a --- /dev/null +++ b/ansible/roles/cloudflared/tasks/main.yml @@ -0,0 +1,41 @@ +--- +- name: Validate Cloudflare Tunnel token is provided + ansible.builtin.assert: + that: + - cloudflared_tunnel_token is defined + - cloudflared_tunnel_token | length > 0 + fail_msg: >- + cloudflared_tunnel_token is required. Inject it through Ansible Vault, + inventory secrets, or -e; do not commit it to git. + no_log: true + +- name: Create cloudflared project directory + ansible.builtin.file: + path: "{{ cloudflared_project_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Render cloudflared compose file + ansible.builtin.template: + src: compose.yml.j2 + dest: "{{ cloudflared_project_dir }}/compose.yml" + owner: root + group: root + mode: "0600" + no_log: true + +- name: Pull cloudflared image + ansible.builtin.command: docker compose pull + args: + chdir: "{{ cloudflared_project_dir }}" + changed_when: true + no_log: true + +- name: Start cloudflared tunnel + ansible.builtin.command: docker compose up -d + args: + chdir: "{{ cloudflared_project_dir }}" + changed_when: true + no_log: true diff --git a/ansible/roles/cloudflared/templates/compose.yml.j2 b/ansible/roles/cloudflared/templates/compose.yml.j2 new file mode 100644 index 0000000..86e3f2c --- /dev/null +++ b/ansible/roles/cloudflared/templates/compose.yml.j2 @@ -0,0 +1,6 @@ +services: + cloudflared: + image: {{ cloudflared_image }} + container_name: {{ cloudflared_container_name }} + restart: {{ cloudflared_restart_policy }} + command: tunnel --no-autoupdate run --token {{ cloudflared_tunnel_token | quote }} From 32bb4b017f52d0a952580f5a08d7a915ee5f8bd3 Mon Sep 17 00:00:00 2001 From: yeounhyeok Date: Thu, 4 Jun 2026 18:29:18 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20EC2=20=ED=8D=BC=EB=B8=94=EB=A6=AD?= =?UTF-8?q?=20=EC=9D=B8=EB=B0=94=EC=9A=B4=EB=93=9C=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- terraform/modules/ec2/main.tf | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/terraform/modules/ec2/main.tf b/terraform/modules/ec2/main.tf index 9849d71..d384431 100644 --- a/terraform/modules/ec2/main.tf +++ b/terraform/modules/ec2/main.tf @@ -45,36 +45,12 @@ resource "local_sensitive_file" "private_key" { file_permission = "0600" } -# EC2 보안그룹. SSH는 키 기반 인증만 사용하므로 IP 제한은 운영자 판단에 위임. +# EC2 보안그룹. Cloudflare Tunnel 기반 아웃바운드 연결만 사용하므로 퍼블릭 인바운드는 열지 않음. resource "aws_security_group" "this" { name = "${local.name_prefix}-ec2-sg" description = "Security group for ${local.name_prefix} app nodes" vpc_id = var.network_summary.vpc_id - ingress { - description = "SSH (key-based auth only)" - from_port = 22 - to_port = 22 - protocol = "tcp" - cidr_blocks = [var.ssh_allowed_cidr] - } - - ingress { - description = "HTTP" - from_port = 80 - to_port = 80 - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] - } - - ingress { - description = "HTTPS" - from_port = 443 - to_port = 443 - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] - } - egress { description = "All outbound" from_port = 0 From 5bcfbce2311379d8329fbf1689df8a0fdabde947 Mon Sep 17 00:00:00 2001 From: yeounhyeok Date: Thu, 4 Jun 2026 18:40:55 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=EB=B3=B4=EC=95=88=EA=B7=B8=EB=A3=B9?= =?UTF-8?q?=20=EC=9D=B8=EB=B0=94=EC=9A=B4=EB=93=9C=20=EA=B7=9C=EC=B9=99=20?= =?UTF-8?q?=EB=AA=85=EC=8B=9C=EC=A0=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- terraform/modules/ec2/main.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terraform/modules/ec2/main.tf b/terraform/modules/ec2/main.tf index d384431..ec3252b 100644 --- a/terraform/modules/ec2/main.tf +++ b/terraform/modules/ec2/main.tf @@ -51,6 +51,8 @@ resource "aws_security_group" "this" { description = "Security group for ${local.name_prefix} app nodes" vpc_id = var.network_summary.vpc_id + ingress = [] + egress { description = "All outbound" from_port = 0 From 920b192936043089d74c12137fc59cf432a93dd1 Mon Sep 17 00:00:00 2001 From: yeounhyeok Date: Thu, 4 Jun 2026 18:57:06 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20cloudflared=20origin=20routing=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ansible/roles/cloudflared/defaults/main.yml | 2 ++ ansible/roles/cloudflared/templates/compose.yml.j2 | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ansible/roles/cloudflared/defaults/main.yml b/ansible/roles/cloudflared/defaults/main.yml index 0d99527..5a4a26d 100644 --- a/ansible/roles/cloudflared/defaults/main.yml +++ b/ansible/roles/cloudflared/defaults/main.yml @@ -4,6 +4,8 @@ cloudflared_project_dir: /opt/moa/cloudflared cloudflared_container_name: moa-cloudflared cloudflared_image: cloudflare/cloudflared:latest cloudflared_restart_policy: unless-stopped +cloudflared_origin_url: http://localhost:8080 +cloudflared_network_mode: host # Required secret at runtime. Do not commit the real token. cloudflared_tunnel_token: "" diff --git a/ansible/roles/cloudflared/templates/compose.yml.j2 b/ansible/roles/cloudflared/templates/compose.yml.j2 index 86e3f2c..98a0215 100644 --- a/ansible/roles/cloudflared/templates/compose.yml.j2 +++ b/ansible/roles/cloudflared/templates/compose.yml.j2 @@ -3,4 +3,5 @@ services: image: {{ cloudflared_image }} container_name: {{ cloudflared_container_name }} restart: {{ cloudflared_restart_policy }} - command: tunnel --no-autoupdate run --token {{ cloudflared_tunnel_token | quote }} + network_mode: {{ cloudflared_network_mode }} + command: tunnel --no-autoupdate --url {{ cloudflared_origin_url | quote }} run --token {{ cloudflared_tunnel_token | quote }} From 4cacad23525b6b369cfd9b540e1aa61bcd3d6d18 Mon Sep 17 00:00:00 2001 From: yeounhyeok Date: Thu, 4 Jun 2026 19:04:40 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20EC2=20SSH=20=EC=9D=B8=EB=B0=94?= =?UTF-8?q?=EC=9A=B4=EB=93=9C=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- terraform/modules/ec2/main.tf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/terraform/modules/ec2/main.tf b/terraform/modules/ec2/main.tf index ec3252b..3d70734 100644 --- a/terraform/modules/ec2/main.tf +++ b/terraform/modules/ec2/main.tf @@ -51,7 +51,13 @@ resource "aws_security_group" "this" { description = "Security group for ${local.name_prefix} app nodes" vpc_id = var.network_summary.vpc_id - ingress = [] + ingress { + description = "SSH access for operations" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } egress { description = "All outbound" From 7d6afa966b94963477a134183c6ab2dd94f73ae3 Mon Sep 17 00:00:00 2001 From: yeounhyeok Date: Thu, 4 Jun 2026 19:27:36 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20MOA=20=ED=84=B0=EB=84=90=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ansible/roles/cloudflared/defaults/main.yml | 1 + ansible/roles/cloudflared/templates/compose.yml.j2 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/roles/cloudflared/defaults/main.yml b/ansible/roles/cloudflared/defaults/main.yml index 5a4a26d..d986241 100644 --- a/ansible/roles/cloudflared/defaults/main.yml +++ b/ansible/roles/cloudflared/defaults/main.yml @@ -5,6 +5,7 @@ cloudflared_container_name: moa-cloudflared cloudflared_image: cloudflare/cloudflared:latest cloudflared_restart_policy: unless-stopped cloudflared_origin_url: http://localhost:8080 +cloudflared_hostname: moa.yeoun.org cloudflared_network_mode: host # Required secret at runtime. Do not commit the real token. diff --git a/ansible/roles/cloudflared/templates/compose.yml.j2 b/ansible/roles/cloudflared/templates/compose.yml.j2 index 98a0215..752ebd7 100644 --- a/ansible/roles/cloudflared/templates/compose.yml.j2 +++ b/ansible/roles/cloudflared/templates/compose.yml.j2 @@ -4,4 +4,4 @@ services: container_name: {{ cloudflared_container_name }} restart: {{ cloudflared_restart_policy }} network_mode: {{ cloudflared_network_mode }} - command: tunnel --no-autoupdate --url {{ cloudflared_origin_url | quote }} run --token {{ cloudflared_tunnel_token | quote }} + command: tunnel --no-autoupdate --url {{ cloudflared_origin_url | quote }} --http-host-header {{ cloudflared_hostname | quote }} run --token {{ cloudflared_tunnel_token | quote }} From 066c06c8034944afb60604dbb26e0b62685e2859 Mon Sep 17 00:00:00 2001 From: yeounhyeok Date: Thu, 4 Jun 2026 19:45:55 +0900 Subject: [PATCH 7/7] =?UTF-8?q?ci:=20Ansible=20PostgreSQL=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/validate.yml | 82 +++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 47ee844..a009549 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,41 +1,41 @@ -name: Validate IaC Scaffold - -on: - pull_request: - push: - branches: - - main - -jobs: - terraform: - runs-on: ubuntu-latest - defaults: - run: - working-directory: terraform/environments/dev - steps: - - uses: actions/checkout@v4 - - uses: hashicorp/setup-terraform@v3 - - name: Terraform fmt check - run: terraform fmt -check -recursive ../.. - - name: Terraform init - run: terraform init -backend=false - - name: Terraform validate - run: terraform validate - - ansible: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install Ansible - run: | - python3 -m pip install --upgrade pip - python3 -m pip install ansible-core - ansible-galaxy collection install community.general - - name: Lint inventory graph - working-directory: ansible - run: ansible-inventory --graph - - name: Syntax check playbooks - working-directory: ansible - run: | - ansible-playbook -i inventories/dev/hosts.yml playbooks/bootstrap.yml --syntax-check - ansible-playbook -i inventories/dev/hosts.yml playbooks/site.yml --syntax-check +name: Validate IaC Scaffold + +on: + pull_request: + push: + branches: + - main + +jobs: + terraform: + runs-on: ubuntu-latest + defaults: + run: + working-directory: terraform/environments/dev + steps: + - uses: actions/checkout@v4 + - uses: hashicorp/setup-terraform@v3 + - name: Terraform fmt check + run: terraform fmt -check -recursive ../.. + - name: Terraform init + run: terraform init -backend=false + - name: Terraform validate + run: terraform validate + + ansible: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Ansible + run: | + python3 -m pip install --upgrade pip + python3 -m pip install ansible-core + ansible-galaxy collection install community.general community.postgresql + - name: Lint inventory graph + working-directory: ansible + run: ansible-inventory --graph + - name: Syntax check playbooks + working-directory: ansible + run: | + ansible-playbook -i inventories/dev/hosts.yml playbooks/bootstrap.yml --syntax-check + ansible-playbook -i inventories/dev/hosts.yml playbooks/site.yml --syntax-check