diff --git a/infra/sync/ansible/inventories/development/group_vars/all/vars.yml b/infra/sync/ansible/inventories/development/group_vars/all/vars.yml index b90453b60..df6e2c0b5 100644 --- a/infra/sync/ansible/inventories/development/group_vars/all/vars.yml +++ b/infra/sync/ansible/inventories/development/group_vars/all/vars.yml @@ -10,6 +10,7 @@ s3_bucket: "octobot-sync-dev" s3_region: "garage" octobot_sync_port: 3000 nginx_port: 80 +nginx_ssl_port: 443 garage_replication_factor: 1 # Map vault → app vars @@ -28,6 +29,7 @@ evm_contract_base: "{{ vault_evm_contract_base | default('') }}" firewall_allowed_tcp_ports: - "22" - "80" + - "443" # Port 3901 (Garage RPC) restricted to peer IPs only — see sync_nodes group vars # Docker (geerlingguy.docker) diff --git a/infra/sync/ansible/roles/stack/tasks/healthcheck.yml b/infra/sync/ansible/roles/stack/tasks/healthcheck.yml index 606907db0..cfcb78132 100644 --- a/infra/sync/ansible/roles/stack/tasks/healthcheck.yml +++ b/infra/sync/ansible/roles/stack/tasks/healthcheck.yml @@ -6,7 +6,7 @@ status_code: [200] register: garage_health until: garage_health.status == 200 - retries: 10 + retries: 30 delay: 5 - name: Wait for OctoBot sync health diff --git a/infra/sync/ansible/roles/stack/tasks/main.yml b/infra/sync/ansible/roles/stack/tasks/main.yml index 3f0763101..a124516e8 100644 --- a/infra/sync/ansible/roles/stack/tasks/main.yml +++ b/infra/sync/ansible/roles/stack/tasks/main.yml @@ -1,8 +1,15 @@ --- +- name: Check if Docker iptables chains are present + ansible.builtin.command: iptables -L DOCKER-USER -n + register: docker_chains + changed_when: false + failed_when: false + - name: Restart Docker to rebuild iptables chains after firewall changes ansible.builtin.service: name: docker state: restarted + when: docker_chains.rc != 0 - name: Create deploy directory ansible.builtin.file: @@ -40,6 +47,25 @@ mode: "0644" notify: restart garage +- name: Render ssl.conf + ansible.builtin.template: + src: ssl.conf.j2 + dest: "{{ stack_deploy_dir }}/ssl.conf" + owner: deploy + group: deploy + mode: "0644" + notify: restart nginx + +- name: Generate self-signed TLS certificate + ansible.builtin.command: + cmd: >- + openssl req -x509 -nodes -newkey rsa:2048 -days 3650 + -keyout {{ stack_deploy_dir }}/origin.key + -out {{ stack_deploy_dir }}/origin.crt + -subj "/CN={{ inventory_hostname }}" + creates: "{{ stack_deploy_dir }}/origin.crt" + notify: restart nginx + - name: Copy collections.json ansible.builtin.copy: src: collections.json diff --git a/infra/sync/ansible/roles/stack/templates/docker-compose.yml.j2 b/infra/sync/ansible/roles/stack/templates/docker-compose.yml.j2 index 93b115a4b..e8c2d3c31 100644 --- a/infra/sync/ansible/roles/stack/templates/docker-compose.yml.j2 +++ b/infra/sync/ansible/roles/stack/templates/docker-compose.yml.j2 @@ -45,8 +45,12 @@ services: image: {{ nginx_image }} ports: - "{{ nginx_port }}:80" + - "{{ nginx_ssl_port }}:443" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + - ./ssl.conf:/etc/nginx/conf.d/ssl.conf:ro + - ./origin.crt:/etc/nginx/certs/origin.crt:ro + - ./origin.key:/etc/nginx/certs/origin.key:ro depends_on: octobot-sync: condition: service_healthy diff --git a/infra/sync/ansible/roles/stack/templates/ssl.conf.j2 b/infra/sync/ansible/roles/stack/templates/ssl.conf.j2 new file mode 100644 index 000000000..b55e3b795 --- /dev/null +++ b/infra/sync/ansible/roles/stack/templates/ssl.conf.j2 @@ -0,0 +1,46 @@ +server { + listen {{ nginx_ssl_port }} ssl; + server_name _; + + ssl_certificate /etc/nginx/certs/origin.crt; + ssl_certificate_key /etc/nginx/certs/origin.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + client_max_body_size 10m; + + # Health (no cache, no rate limit) + location = /health { + proxy_pass http://octobot_sync; + } + + # Push endpoints (strict rate limit) + location ~* ^/v1/push/ { + proxy_pass http://octobot_sync; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + limit_req zone=sync_strict burst=25 nodelay; + } + + # Pull endpoints (general rate limit + cache) + location ~* ^/v1/pull/ { + proxy_pass http://octobot_sync; + proxy_cache sync_cache; + proxy_cache_valid 200 1h; + proxy_cache_use_stale error timeout updating; + proxy_cache_lock on; + add_header X-Cache-Status $upstream_cache_status; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + limit_req zone=sync_global burst=50 nodelay; + } + + # Reject anything outside /v1 and /health + location / { + return 404; + } +}