diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0f1821f..3b8033f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ variables: GIT_SUBMODULE_STRATEGY: none CI_DISPOSABLE_ENVIRONMENT: "true" -image: blockstream/gcloud-docker@sha256:2ab8222c44502282a614cdda4a9f1434d6f91e93888a39c56b82ebc52f6bd3b1 +image: blockstream/gcloud-docker@sha256:e12152fa1daade1a1e3ff1c61ccc434fd99d7ad3627508fd2aa59f0f6b79910b stages: - build - plan @@ -77,13 +77,14 @@ plan_satapi: -var "zone=$ZONE" -var "instance_type=$INSTANCE_TYPE" -var "host=$HOST_STAGING" - -var "public_bucket_url=$PUBLIC_BUCKET_URL" - -var "letsencrypt_email=$LE_EMAIL" -var "timeout=$TIMEOUT" -var "prom_service_acct=$PROM_SA" -var "opsgenie_key=$OPSGENIE_KEY" -var "rpcuser=$RPCUSER" -var "rpcpass=$RPCPASS" + -var "public_bucket_url=$PUBLIC_BUCKET_URL" + -var "letsencrypt_email=$LE_EMAIL" + -var "lb_svc_acct=$LB_SA_STAGING" -input=false) # This plan gets triggered only for miscellaneous branches/tags (i.e. tor, prometheus, etc), so make sure the branch/tag name starts with misc_ @@ -134,7 +135,7 @@ deploy_misc: -var "satellite_lb=$SATELLITE_LB" -var "satellite_api_lb=$SATELLITE_API_LB" -var "satellite_api_lb_staging=$SATELLITE_API_LB_STAGING" - -input=false -auto-approve) + -input=false -auto-approve) # Tag with staging_v.* to deploy mainnet staging (e.g. staging_v0.1.1) deploy_staging: @@ -152,13 +153,13 @@ deploy_staging: -var "zone=$ZONE" -var "instance_type=$INSTANCE_TYPE" -var "host=$HOST_STAGING" - -var "public_bucket_url=$PUBLIC_BUCKET_URL" - -var "letsencrypt_email=$LE_EMAIL" -var "timeout=$TIMEOUT" -var "prom_service_acct=$PROM_SA" -var "opsgenie_key=$OPSGENIE_KEY" -var "rpcuser=$RPCUSER" -var "rpcpass=$RPCPASS" + -var "public_bucket_url=$PUBLIC_BUCKET_URL" + -var "letsencrypt_email=$LE_EMAIL" -input=false -auto-approve) # Tag with production_v.* to deploy mainnet production (e.g. prod_v0.1.1) @@ -177,13 +178,13 @@ deploy_production: -var "zone=$ZONE" -var "instance_type=$INSTANCE_TYPE" -var "host=$HOST" - -var "public_bucket_url=$PUBLIC_BUCKET_URL" - -var "letsencrypt_email=$LE_EMAIL" -var "timeout=$TIMEOUT" -var "prom_service_acct=$PROM_SA" -var "opsgenie_key=$OPSGENIE_KEY" -var "rpcuser=$RPCUSER" -var "rpcpass=$RPCPASS" + -var "public_bucket_url=$PUBLIC_BUCKET_URL" + -var "letsencrypt_email=$LE_EMAIL" -input=false -auto-approve) # Tag with testnet_staging_v.* to deploy testnet staging (e.g. testnet_staging_v0.1.1) @@ -202,13 +203,14 @@ deploy_staging_testnet: -var "zone=$ZONE" -var "instance_type=$INSTANCE_TYPE" -var "host=$HOST_STAGING" - -var "public_bucket_url=$PUBLIC_BUCKET_URL" - -var "letsencrypt_email=$LE_EMAIL" -var "timeout=$TIMEOUT" -var "prom_service_acct=$PROM_SA" -var "opsgenie_key=$OPSGENIE_KEY" -var "rpcuser=$RPCUSER" -var "rpcpass=$RPCPASS" + -var "public_bucket_url=$PUBLIC_BUCKET_URL" + -var "letsencrypt_email=$LE_EMAIL" + -var "lb_svc_acct=$LB_SA_STAGING" -input=false -auto-approve) # Tag with production_v.* to deploy testnet production (e.g. testnet_prod_v0.1.1) @@ -226,22 +228,20 @@ deploy_production_testnet: -var "region=$REGION" -var "zone=$ZONE" -var "instance_type=$INSTANCE_TYPE" - -var "host=$HOST" - -var "public_bucket_url=$PUBLIC_BUCKET_URL" - -var "letsencrypt_email=$LE_EMAIL" + -var "host=$HOST_STAGING" -var "timeout=$TIMEOUT" -var "prom_service_acct=$PROM_SA" -var "opsgenie_key=$OPSGENIE_KEY" -var "rpcuser=$RPCUSER" -var "rpcpass=$RPCPASS" + -var "public_bucket_url=$PUBLIC_BUCKET_URL" + -var "letsencrypt_email=$LE_EMAIL" + -var "lb_svc_acct=$LB_SA" -input=false -auto-approve) # Pushing to this branch destroys the staging infrastructure cleanup_staging: stage: deploy - image: - name: hashicorp/terraform:light - entrypoint: [""] only: - cleanup_staging@satellite/ionosphere script: @@ -249,6 +249,7 @@ cleanup_staging: terraform workspace select staging && terraform destroy -target module.blc.google_compute_instance_group_manager.blc + -target module.lb.google_compute_region_instance_group_manager.satapi-lb -auto-approve) - (cd terraform && terraform init -input=false && terraform workspace select testnet-staging && diff --git a/terraform/data.tf b/terraform/data.tf index 8a3b0d8..82cdca0 100644 --- a/terraform/data.tf +++ b/terraform/data.tf @@ -1,22 +1,19 @@ data "terraform_remote_state" "blc-mainnet" { - backend = "gcs" + backend = "gcs" + workspace = local.env config = { - bucket = "tf-state-satellite-api" - prefix = "terraform/state" + bucket = "terraform-bs-source" + prefix = "satellite-api" } - - workspace = "prod" } data "terraform_remote_state" "blc-testnet" { - backend = "gcs" + backend = "gcs" + workspace = "testnet-${local.env}" config = { - bucket = "tf-state-satellite-api" - prefix = "terraform/state" + bucket = "terraform-bs-source" + prefix = "satellite-api" } - - workspace = "testnet-prod" } - diff --git a/terraform/main.tf b/terraform/main.tf index 3413b83..bfc2f0b 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -27,10 +27,9 @@ module "blc-mainnet" { ionosphere_docker = var.ionosphere_docker ionosphere_sse_docker = var.ionosphere_sse_docker node_exporter_docker = var.node_exporter_docker - certbot_docker = var.certbot_docker net = "mainnet" env = local.env - target_pool = google_compute_target_pool.blc-pool[0].self_link + lb_svc_acct = module.lb.lb_svc_acct create_resources = local.create_mainnet @@ -38,14 +37,11 @@ module "blc-mainnet" { region = var.region zone = var.zone instance_type = var.instance_type[0] - host = var.host timeout = var.timeout prom_service_acct = var.prom_service_acct opsgenie_key = var.opsgenie_key rpcuser = var.rpcuser rpcpass = var.rpcpass - letsencrypt_email = var.letsencrypt_email - public_bucket_url = var.public_bucket_url } module "blc-testnet" { @@ -60,10 +56,8 @@ module "blc-testnet" { ionosphere_docker = var.ionosphere_docker ionosphere_sse_docker = var.ionosphere_sse_docker node_exporter_docker = var.node_exporter_docker - certbot_docker = var.certbot_docker net = "testnet" env = local.env - target_pool = google_compute_target_pool.blc-pool[0].self_link create_resources = local.create_testnet @@ -71,12 +65,36 @@ module "blc-testnet" { region = var.region zone = var.zone instance_type = var.instance_type[0] - host = var.host timeout = var.timeout prom_service_acct = var.prom_service_acct opsgenie_key = var.opsgenie_key rpcuser = var.rpcuser rpcpass = var.rpcpass + lb_svc_acct = var.lb_svc_acct +} + +module "lb" { + source = "./modules/lb" + + project = var.project + name = "satellite-api-lb" + network = "default" + certbot_docker = var.certbot_docker + node_exporter_docker = var.node_exporter_docker + env = local.env + internal_ip_mainnet = module.blc-mainnet.internal_ip + internal_ip_testnet = data.terraform_remote_state.blc-testnet.outputs.blc_internal_ip_testnet + target_pool = google_compute_target_pool.lb-pool[0].self_link + + create_resources = local.create_mainnet + + # CI vars + region = var.region + zone = var.zone + instance_type = var.instance_type[1] + host = var.host + timeout = var.timeout + prom_service_acct = var.prom_service_acct letsencrypt_email = var.letsencrypt_email public_bucket_url = var.public_bucket_url } diff --git a/terraform/modules/blc/cloud-init/blc.yaml b/terraform/modules/blc/cloud-init/blc.yaml index 2ffc6fd..b774dbd 100644 --- a/terraform/modules/blc/cloud-init/blc.yaml +++ b/terraform/modules/blc/cloud-init/blc.yaml @@ -1,4 +1,4 @@ -certbot_dockerbootcmd: +bootcmd: - blkid /dev/disk/by-id/google-data || mkfs.ext4 -L data /dev/disk/by-id/google-data - mkdir -p /mnt/disks/data mounts: @@ -28,239 +28,6 @@ write_files: announce-addr=${announce_addr} bind-addr=0.0.0.0 - - path: /home/bs/default.conf - permissions: 0644 - owner: root - content: | - log_format withtime '$remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" ' - 'rt="$request_time" uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; - - server { - index index.php index.html index.htm index.nginx-debian.html; - - access_log /var/log/nginx/access-def.log withtime; - error_log /var/log/nginx/error-def.log; - - server_name ${host}; - listen 80; - server_tokens off; - proxy_set_header X-Forwarded-For 0.0.0.0; - - location /.well-known { - auth_basic off; - allow all; # Allow all to see content - proxy_pass ${public_bucket_url}/certs/.well-known; - } - - location /healthz { - return 200; - } - - location / { - return 301 https://$host$request_uri; - } - } - - - path: /home/bs/space.conf - permissions: 0644 - owner: root - content: | - log_format withtime '$remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" ' - 'rt="$request_time" uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; - - server { - access_log /var/log/nginx/access.log withtime; - error_log /var/log/nginx/error.log; - - server_name ${host}; - listen 443 ssl default_server; - ssl_certificate /etc/nginx/certs/live/${host}/fullchain.pem; - ssl_certificate_key /etc/nginx/certs/live/${host}/privkey.pem; - ssl_prefer_server_ciphers on; - ssl_dhparam /etc/nginx/certs/certs/dhparam.pem; - - root /usr/share/nginx/html/; - server_tokens off; - proxy_set_header X-Forwarded-For 0.0.0.0; - - # Proxy to Satellite API - location = / { - rewrite ^ /index.html break; - } - - location ${url_path}/ { - add_header 'Access-Control-Allow-Origin' '*' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE' always; - add_header 'Access-Control-Allow-Headers' 'X-Auth-Token,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; - add_header 'X-XSS-Protection' '1; mode=block' always; - - if ($request_uri ~* "^${url_path}/?$") - { - return 301 https://$host$request_uri; - } - - if ($request_method = 'OPTIONS') - { - return 200; - } - - proxy_pass http://0.0.0.0:9292/; - } - - # Proxy to SSE container - location ${url_path}/subscribe/ { - chunked_transfer_encoding off; - proxy_buffering off; - proxy_request_buffering off; - proxy_cache off; - proxy_http_version 1.1; - - proxy_pass http://0.0.0.0:4500/stream?channels=; - } - } - - - path: /home/bs/index.html - permissions: 0644 - owner: root - content: | - - - Blockstream Satellite API - -

This is our fancy default page. Here are some interesting places worth exploring:

- -

Learn more: Satellite API Docs

-

Contribute: Satellite API Code

-
-

If you're trying to use Tor, make sure you're using the right path. For example:

-

http://btcspaceda7iejsrb7ihmi5si3hhssxxxdnqvvtyz6prv2m73j7lcoqd.onion/orders/pending

- - - - - - path: /etc/systemd/system/nginx.service - permissions: 0644 - owner: root - content: | - [Unit] - Description=Nginx redirect - Wants=gcr-online.target - After=ionosphere.service - - [Service] - Restart=always - RestartSec=3 - Environment=HOME=/home/bs - ExecStartPre=/sbin/iptables -A INPUT -m tcp -p tcp --dport 80 -j ACCEPT - ExecStart=/usr/bin/docker run \ - --network=host \ - --pid=host \ - --name=nginx \ - --log-opt max-size=200m \ - --log-opt max-file=3 \ - -v /home/bs/default.conf:/etc/nginx/conf.d/default.conf:ro \ - "nginx:latest" - ExecStop=/usr/bin/docker stop nginx - ExecStopPost=/usr/bin/docker rm nginx - ExecStopPost=/sbin/iptables -D INPUT -m tcp -p tcp --dport 80 -j ACCEPT - - - path: /etc/systemd/system/nginx-tls.service - permissions: 0644 - owner: root - content: | - [Unit] - Description=Nginx TLS proxy - Wants=gcr-online.target - After=nginx.service - - [Service] - Restart=always - RestartSec=3 - Environment=HOME=/home/bs - ExecStartPre=/sbin/iptables -A INPUT -m tcp -p tcp --dport 443 -j ACCEPT - ExecStart=/usr/bin/docker run \ - --network=host \ - --pid=host \ - --name=nginx-tls \ - --log-opt max-size=200m \ - --log-opt max-file=3 \ - -v /home/bs/space.conf:/etc/nginx/conf.d/default.conf:ro \ - -v /home/bs/index.html:/usr/share/nginx/html/index.html:ro \ - -v /home/bs/certs:/etc/nginx/certs:ro \ - "nginx:latest" - ExecStop=/usr/bin/docker stop nginx-tls - ExecStopPost=/usr/bin/docker rm nginx-tls - ExecStopPost=/sbin/iptables -D INPUT -m tcp -p tcp --dport 443 -j ACCEPT - - - - path: /etc/systemd/system/cert-downloader.service - permissions: 0644 - owner: root - content: | - [Unit] - Description=Run cert-downloader - Wants=gcr-online.target - After=nginx-tls.service - - [Service] - Type=oneshot - RemainAfterExit=true - Environment=HOME=/home/bs - ExecStartPre=/usr/bin/docker-credential-gcr configure-docker - ExecStart=/usr/bin/docker run \ - --name=cert-downloader \ - --tmpfs /root \ - --tmpfs /tmp \ - --rm \ - -v /home/bs/certs:/etc/letsencrypt:rw \ - -e GCS_PUBLIC_BUCKET=${public_bucket} \ - -e GCS_PRIVATE_BUCKET=${private_bucket} \ - -e DOMAIN=${host} \ - "${certbot_docker}" download - - - path: /etc/systemd/system/cert-renewer.timer - permissions: 0644 - owner: root - content: | - [Unit] - Description=Run full cert-renewer every 24 hours - - [Timer] - OnUnitActiveSec=1d - Persistent=true - - [Install] - WantedBy=timers.target - - - path: /etc/systemd/system/cert-renewer.service - permissions: 0644 - owner: root - content: | - [Unit] - Description=Run cert-renewer - Wants=gcr-online.target - After=gcr-online.target - - [Service] - Type=oneshot - Environment=HOME=/home/bs - ExecStartPre=/usr/bin/docker-credential-gcr configure-docker - ExecStart=/usr/bin/docker run \ - --name=cert-renewer \ - --tmpfs /root \ - --tmpfs /tmp \ - --rm \ - -v /home/bs/certs:/etc/letsencrypt:rw \ - -e GCS_PUBLIC_BUCKET=${public_bucket} \ - -e GCS_PRIVATE_BUCKET=${private_bucket} \ - -e DOMAIN=${host} \ - -e EMAIL=${letsencrypt_email} \ - "${certbot_docker}" renew - ExecStartPost=-/usr/bin/systemctl restart nginx-tls - path: /home/bs/check_containers.sh permissions: 0744 @@ -284,7 +51,7 @@ write_files: "alias": "satapi-missing-containers", "description":"Currently running '$${NUM_CONT}'/10: '$${RUNNING_CONT}'", "tags": ["SatAPI","Critical"], - "entity":"satellite.blockstream.com/api", + "entity":"api.blockstream.space", "priority":"P3" }' else @@ -439,7 +206,8 @@ write_files: RestartSec=3 Environment=HOME=/home/bs ExecStartPre=/usr/bin/docker pull ${ionosphere_docker} - ExecStartPre=/sbin/iptables -A INPUT -p tcp -s localhost --dport 9292 -j ACCEPT + ExecStartPre=/sbin/iptables -A INPUT -p tcp -s 10.138.0.0/16 --dport 9292 -j ACCEPT + ExecStopPost=/sbin/iptables -A INPUT -p tcp -s 10.138.0.0/16 --dport 4500 -j ACCEPT ExecStartPre=/usr/bin/docker run \ --user root \ -v /mnt/disks/data/ionosphere:/data \ @@ -460,7 +228,8 @@ write_files: "${ionosphere_docker}" ExecStop=/usr/bin/docker stop ionosphere ExecStopPost=/usr/bin/docker rm ionosphere - ExecStopPost=/sbin/iptables -D INPUT -p tcp -s localhost --dport 9292 -j ACCEPT + ExecStopPost=/sbin/iptables -D INPUT -p tcp -s 10.138.0.0/16 --dport 9292 -j ACCEPT + ExecStopPost=/sbin/iptables -D INPUT -p tcp -s 10.138.0.0/16 --dport 4500 -j ACCEPT - path: /etc/systemd/system/ionosphere-tx.service permissions: 0644 @@ -552,14 +321,6 @@ runcmd: - systemctl enable ionosphere-sse.service - systemctl start charge.service - systemctl enable charge.service - - systemctl start nginx.service - - systemctl enable nginx.service - - systemctl start cert-renewer.timer - - systemctl enable cert-renewer.timer - - systemctl start cert-downloader.service - - systemctl enable cert-downloader.service - - systemctl start nginx-tls.service - - systemctl enable nginx-tls.service - systemctl start node-exporter.service - systemctl enable node-exporter.service - systemctl start check-containers.timer diff --git a/terraform/modules/blc/data.tf b/terraform/modules/blc/data.tf index 37db984..9f68daf 100644 --- a/terraform/modules/blc/data.tf +++ b/terraform/modules/blc/data.tf @@ -17,7 +17,6 @@ data "template_file" "blc" { rpcuser = var.rpcuser rpcpass = var.rpcpass net = var.net - url_path = var.net == "testnet" ? "/testnet" : "" bitcoin_cmd = "bitcoind ${var.net == "testnet" ? "-testnet" : ""} -printtoconsole" lightning_cmd = "lightningd ${var.net == "testnet" ? "--testnet" : "--mainnet"} --conf=/root/.lightning/lightning.conf --plugin-dir=/usr/local/bin/plugins" charge_cmd = "charged -d /data/charge.db -l /root/.lightning" @@ -26,17 +25,11 @@ data "template_file" "blc" { bitcoin_docker = var.bitcoin_docker lightning_docker = var.lightning_docker charge_docker = var.charge_docker - certbot_docker = var.certbot_docker redis_port = 6379 ionosphere_docker = var.ionosphere_docker ionosphere_sse_docker = var.ionosphere_sse_docker node_exporter_docker = var.node_exporter_docker opsgenie_key = var.opsgenie_key - host = var.host - public_bucket_url = "${var.public_bucket_url}-${var.env}" - public_bucket = replace(google_storage_bucket.blc-public[count.index].url, "gs://", "") - private_bucket = replace(google_storage_bucket.blc-private[count.index].url, "gs://", "") - letsencrypt_email = var.letsencrypt_email } } @@ -50,4 +43,3 @@ data "template_cloudinit_config" "blc" { content = data.template_file.blc[0].rendered } } - diff --git a/terraform/modules/blc/firewall.tf b/terraform/modules/blc/firewall.tf index 69a3b91..df60014 100644 --- a/terraform/modules/blc/firewall.tf +++ b/terraform/modules/blc/firewall.tf @@ -1,11 +1,12 @@ resource "google_compute_firewall" "blc" { name = "${var.name}-${var.net}-fw-rule-${var.env}" network = data.google_compute_network.blc.self_link + project = var.project count = var.create_resources allow { protocol = "tcp" - ports = ["18333", "8333", "9735", "80", "443"] + ports = ["18333", "8333", "9735"] } target_service_accounts = [ @@ -13,9 +14,30 @@ resource "google_compute_firewall" "blc" { ] } +resource "google_compute_firewall" "api-internal" { + name = "${var.name}-${var.net}-lb-internal-fw-rule-${var.env}" + network = data.google_compute_network.blc.self_link + project = var.project + count = var.create_resources + + allow { + protocol = "tcp" + ports = ["9292", "4500"] + } + + source_service_accounts = [ + var.lb_svc_acct, + ] + + target_service_accounts = [ + google_service_account.blc[0].email, + ] +} + resource "google_compute_firewall" "blc-prom" { name = "${var.name}-${var.net}-prometheus-access-${var.env}" network = data.google_compute_network.blc.self_link + project = var.project count = var.create_resources allow { diff --git a/terraform/modules/blc/main.tf b/terraform/modules/blc/main.tf index ed44126..54a8de0 100644 --- a/terraform/modules/blc/main.tf +++ b/terraform/modules/blc/main.tf @@ -1,10 +1,22 @@ -# Instance group +resource "google_compute_disk" "blc" { + name = "${var.name}-data-${var.net}-${var.env}" + type = "pd-standard" + image = data.google_compute_image.blc[0].self_link + zone = var.zone + count = var.create_resources + + lifecycle { + prevent_destroy = true + ignore_changes = [image] + } +} + +# Instance group & template resource "google_compute_instance_group_manager" "blc" { - name = "${var.name}-ig-${var.net}-${var.env}" - target_pools = [var.target_pool] - project = var.project - provider = google-beta - count = var.create_resources + name = "${var.name}-ig-${var.net}-${var.env}" + project = var.project + provider = google-beta + count = var.create_resources base_instance_name = "${var.name}-ig-${var.net}-${var.env}" zone = var.zone @@ -24,20 +36,6 @@ resource "google_compute_instance_group_manager" "blc" { } } -resource "google_compute_disk" "blc" { - name = "${var.name}-data-${var.net}-${var.env}" - type = "pd-standard" - image = data.google_compute_image.blc[0].self_link - zone = var.zone - count = var.create_resources - - lifecycle { - prevent_destroy = true - ignore_changes = [image] - } -} - -# Instance template resource "google_compute_instance_template" "blc" { name_prefix = "${var.name}-${var.net}-${var.env}-tmpl-" description = "This template is used to create ${var.name} ${var.net} ${var.env} instances." diff --git a/terraform/modules/blc/network.tf b/terraform/modules/blc/network.tf index 3338b80..a5daa7c 100644 --- a/terraform/modules/blc/network.tf +++ b/terraform/modules/blc/network.tf @@ -1,13 +1,13 @@ # External and internal static IPs resource "google_compute_address" "blc" { - name = "${var.name}-${var.net}-external-ip-${var.env}-${count.index}" + name = "${var.name}-${var.net}-external-ip-${var.env}" project = var.project region = var.region count = var.create_resources } resource "google_compute_address" "blc-internal" { - name = "${var.name}-${var.net}-internal-ip-${var.env}-${count.index}" + name = "${var.name}-${var.net}-internal-ip-${var.env}" address_type = "INTERNAL" project = var.project region = var.region diff --git a/terraform/modules/blc/outputs.tf b/terraform/modules/blc/outputs.tf index f61b97c..96f363e 100644 --- a/terraform/modules/blc/outputs.tf +++ b/terraform/modules/blc/outputs.tf @@ -1,7 +1,7 @@ output "backend_service" { - value = element( - concat(google_compute_backend_service.blc.*.self_link, [""]), - 0, - ) + value = google_compute_backend_service.blc[0].self_link } +output "internal_ip" { + value = google_compute_address.blc-internal[0].address +} diff --git a/terraform/modules/blc/variables.tf b/terraform/modules/blc/variables.tf index 6107e31..1b8cc55 100644 --- a/terraform/modules/blc/variables.tf +++ b/terraform/modules/blc/variables.tf @@ -48,14 +48,6 @@ variable "net" { type = string } -variable "target_pool" { - type = string -} - -variable "host" { - type = string -} - variable "timeout" { type = string } @@ -68,11 +60,7 @@ variable "prom_service_acct" { type = string } -variable "public_bucket_url" { - type = string -} - -variable "letsencrypt_email" { +variable "lb_svc_acct" { type = string } @@ -98,8 +86,4 @@ variable "ionosphere_sse_docker" { variable "node_exporter_docker" { type = string -} - -variable "certbot_docker" { - type = string -} +} \ No newline at end of file diff --git a/terraform/modules/lb/cloud-init/lb.yaml b/terraform/modules/lb/cloud-init/lb.yaml new file mode 100644 index 0000000..a798ea9 --- /dev/null +++ b/terraform/modules/lb/cloud-init/lb.yaml @@ -0,0 +1,343 @@ +users: + - name: bs + uid: 2000 + +write_files: + - path: /home/bs/default.conf + permissions: 0644 + owner: root + content: | + log_format withtime '$remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt="$request_time" uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; + + server { + access_log /var/log/nginx/access-80.log withtime; + error_log /var/log/nginx/error-80.log; + root /usr/share/nginx/html/; + + server_name ${host}; + listen 80; + server_tokens off; + proxy_set_header X-Forwarded-For 0.0.0.0; + + location / { + return 301 https://$host$request_uri; + } + + location /.well-known { + auth_basic off; + allow all; # Allow all to see content + proxy_pass ${public_bucket_url}/certs/.well-known; + } + + location /healthz { + return 200; + } + } + + - path: /home/bs/default-tls.conf + permissions: 0644 + owner: root + content: | + log_format withtime '$remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt="$request_time" uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; + + server { + index index.php index.html index.htm index.nginx-debian.html; + + access_log /var/log/nginx/access.log withtime; + error_log /var/log/nginx/error.log; + + server_name ${host}; + listen 443 ssl default_server; + ssl_certificate /etc/nginx/certs/live/${host}/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/live/${host}/privkey.pem; + ssl_prefer_server_ciphers on; + ssl_dhparam /etc/nginx/certs/certs/dhparam.pem; + + ssl_client_certificate /etc/nginx/certs/ca.cert; + ssl_verify_client optional; + + root /usr/share/nginx/html/; + server_tokens off; + proxy_set_header X-Forwarded-For 0.0.0.0; + + location = / { + rewrite ^ /index.html break; + } + + # Client cert authenticated endpoints + location /order/tx/ { + # Allow base stations only + allow 202.161.136.2; + allow 66.203.141.162; + + if ($ssl_client_verify != SUCCESS) { + return 403; + } + proxy_pass http://${mainnet_ip}:9292/; + } + + location /order/rx/ { + # Allow base stations only + allow 202.161.136.2; + allow 66.203.141.162; + + if ($ssl_client_verify != SUCCESS) { + return 403; + } + proxy_pass http://${mainnet_ip}:9292/; + } + + # Proxy to mainnet Satellite API + location / { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE' always; + add_header 'Access-Control-Allow-Headers' 'X-Auth-Token,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'X-XSS-Protection' '1; mode=block' always; + + if ($request_uri ~* "^/$") + { + return 301 https://$host; + } + + if ($request_method = 'OPTIONS') + { + return 200; + } + + proxy_pass http://${mainnet_ip}:9292/; + } + + # Proxy to mainnet SSE container + location /subscribe/ { + chunked_transfer_encoding off; + proxy_buffering off; + proxy_request_buffering off; + proxy_cache off; + proxy_http_version 1.1; + + proxy_pass http://${mainnet_ip}:4500/stream?channels=; + } + + # Proxy to testnet Satellite API + location /testnet/ { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE' always; + add_header 'Access-Control-Allow-Headers' 'X-Auth-Token,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'X-XSS-Protection' '1; mode=block' always; + + if ($request_uri ~* "^/testnet/?$") + { + return 301 https://$host; + } + + if ($request_method = 'OPTIONS') + { + return 200; + } + + proxy_pass http://${testnet_ip}:9292/; + } + + # Proxy to mainnet SSE container + location /testnet/subscribe/ { + chunked_transfer_encoding off; + proxy_buffering off; + proxy_request_buffering off; + proxy_cache off; + proxy_http_version 1.1; + + proxy_pass http://${testnet_ip}:4500/stream?channels=; + } + } + + - path: /home/bs/index.html + permissions: 0644 + owner: root + content: | + + + Blockstream Satellite API + +

This is our fancy default page. Here are some interesting places worth exploring:

+ +

Learn more: Satellite API Docs

+

Contribute: Satellite API Code

+

Mainnet:

+ https://api.blockstream.space +

Testnet (replacement for https://satellite.blockstream.com/api):

+ https://api.blockstream.space/testnet +

If you're trying to use Tor, make sure you're using the right path. For example:

+ http://btcspaceda7iejsrb7ihmi5si3hhssxxxdnqvvtyz6prv2m73j7lcoqd.onion/testnet/info + + + + + - path: /etc/systemd/system/nginx-tls.service + permissions: 0644 + owner: root + content: | + [Unit] + Description=Nginx redirect + Wants=gcr-online.target + After=ionosphere.service + + [Service] + Restart=always + RestartSec=3 + Environment=HOME=/home/bs + ExecStartPre=/sbin/iptables -A INPUT -m tcp -p tcp --dport 443 -j ACCEPT + ExecStart=/usr/bin/docker run \ + --network=host \ + --pid=host \ + --name=nginx-tls \ + --log-opt max-size=200m \ + --log-opt max-file=3 \ + -v /home/bs/default-tls.conf:/etc/nginx/conf.d/default.conf:ro \ + -v /home/bs/index.html:/usr/share/nginx/html/index.html:ro \ + -v /home/bs/certs:/etc/nginx/certs:ro \ + "nginx:latest" + ExecStop=/usr/bin/docker stop nginx-tls + ExecStopPost=/usr/bin/docker rm nginx-tls + ExecStopPost=/sbin/iptables -D INPUT -m tcp -p tcp --dport 443 -j ACCEPT + + - path: /etc/systemd/system/nginx.service + permissions: 0644 + owner: root + content: | + [Unit] + Description=Nginx redirect + Wants=gcr-online.target + After=ionosphere.service + + [Service] + Restart=always + RestartSec=3 + Environment=HOME=/home/bs + ExecStartPre=/sbin/iptables -A INPUT -m tcp -p tcp --dport 80 -j ACCEPT + ExecStart=/usr/bin/docker run \ + --network=host \ + --pid=host \ + --name=nginx \ + --log-opt max-size=200m \ + --log-opt max-file=3 \ + -v /home/bs/default.conf:/etc/nginx/conf.d/default.conf:ro \ + -v /home/bs/index.html:/usr/share/nginx/html/index.html:ro \ + "nginx:latest" + ExecStop=/usr/bin/docker stop nginx + ExecStopPost=/usr/bin/docker rm nginx + ExecStopPost=/sbin/iptables -D INPUT -m tcp -p tcp --dport 80 -j ACCEPT + + - path: /etc/systemd/system/cert-downloader.service + permissions: 0644 + owner: root + content: | + [Unit] + Description=Run cert-downloader + Wants=gcr-online.target + After=nginx-tls.service + + [Service] + Type=oneshot + RemainAfterExit=true + Environment=HOME=/home/bs + ExecStartPre=/usr/bin/docker-credential-gcr configure-docker + ExecStart=/usr/bin/docker run \ + --name=cert-downloader \ + --tmpfs /root \ + --tmpfs /tmp \ + --rm \ + -v /home/bs/certs:/etc/letsencrypt:rw \ + -e GCS_PUBLIC_BUCKET=${public_bucket} \ + -e GCS_PRIVATE_BUCKET=${private_bucket} \ + -e DOMAIN=${host} \ + "${certbot_docker}" download + + - path: /etc/systemd/system/cert-renewer.timer + permissions: 0644 + owner: root + content: | + [Unit] + Description=Run full cert-renewer every 24 hours + + [Timer] + OnUnitActiveSec=1d + Persistent=true + + [Install] + WantedBy=timers.target + + - path: /etc/systemd/system/cert-renewer.service + permissions: 0644 + owner: root + content: | + [Unit] + Description=Run cert-renewer + Wants=gcr-online.target + After=gcr-online.target + + [Service] + Type=oneshot + Environment=HOME=/home/bs + ExecStartPre=/usr/bin/docker-credential-gcr configure-docker + ExecStart=/usr/bin/docker run \ + --name=cert-renewer \ + --tmpfs /root \ + --tmpfs /tmp \ + --rm \ + -v /home/bs/certs:/etc/letsencrypt:rw \ + -e GCS_PUBLIC_BUCKET=${public_bucket} \ + -e GCS_PRIVATE_BUCKET=${private_bucket} \ + -e DOMAIN=${host} \ + -e EMAIL=${letsencrypt_email} \ + "${certbot_docker}" renew + ExecStartPost=-/usr/bin/systemctl restart nginx-tls + + - path: /etc/systemd/system/node-exporter.service + permissions: 0644 + owner: root + content: | + [Unit] + Description=Prometheus node-exporter + Wants=gcr-online.target docker.service + After=gcr-online.service docker.service + + [Service] + Restart=always + RestartSec=3 + Environment=HOME=/home/bs + ExecStartPre=/usr/bin/docker pull ${node_exporter_docker} + ExecStartPre=/sbin/iptables -A INPUT -m tcp -p tcp --dport 9100 -j ACCEPT + ExecStart=/usr/bin/docker run \ + --name=node-exporter \ + --network=host \ + --read-only \ + -v /proc:/host/proc:ro \ + -v /sys:/host/sys:ro \ + -v /:/rootfs:ro \ + -v metrics:/metrics:ro \ + -v /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro \ + "${node_exporter_docker}" --path.procfs /host/proc --path.sysfs /host/sys --collector.textfile.directory /metrics --collector.filesystem.ignored-mount-points "^/(sys|proc|dev|host|etc($|/))" --collector.systemd + ExecStop=/usr/bin/docker stop node-exporter + ExecStopPost=/usr/bin/docker rm node-exporter + ExecStopPost=/sbin/iptables -D INPUT -m tcp -p tcp --dport 9100 -j ACCEPT + +runcmd: + - systemctl daemon-reload + - systemctl start nginx.service + - systemctl enable nginx.service + - systemctl start cert-renewer.timer + - systemctl enable cert-renewer.timer + - systemctl start cert-downloader.service + - systemctl enable cert-downloader.service + - systemctl start nginx.service + - systemctl enable nginx.service + - systemctl start nginx-tls.service + - systemctl enable nginx-tls.service + - systemctl start node-exporter.service + - systemctl enable node-exporter.service diff --git a/terraform/modules/lb/data.tf b/terraform/modules/lb/data.tf new file mode 100644 index 0000000..7858e78 --- /dev/null +++ b/terraform/modules/lb/data.tf @@ -0,0 +1,32 @@ +data "google_compute_network" "satapi-lb" { + name = "default" + project = var.project +} + +data "template_file" "satapi-lb" { + template = file("${path.module}/cloud-init/lb.yaml") + count = var.create_resources + + vars = { + mainnet_ip = var.internal_ip_mainnet + testnet_ip = var.internal_ip_testnet + certbot_docker = var.certbot_docker + node_exporter_docker = var.node_exporter_docker + host = var.host + public_bucket_url = "${var.public_bucket_url}-${var.env}" + public_bucket = replace(google_storage_bucket.satapi-lb-public[count.index].url, "gs://", "") + private_bucket = replace(google_storage_bucket.satapi-lb-private[count.index].url, "gs://", "") + letsencrypt_email = var.letsencrypt_email + } +} + +data "template_cloudinit_config" "satapi-lb" { + gzip = false + base64_encode = false + count = var.create_resources + + part { + content_type = "text/cloud-config" + content = data.template_file.satapi-lb[0].rendered + } +} diff --git a/terraform/modules/lb/firewall.tf b/terraform/modules/lb/firewall.tf new file mode 100644 index 0000000..5089e05 --- /dev/null +++ b/terraform/modules/lb/firewall.tf @@ -0,0 +1,36 @@ +resource "google_compute_firewall" "satapi-lb" { + name = "${var.name}-fw-rule-${var.env}" + network = data.google_compute_network.satapi-lb.self_link + project = var.project + count = var.create_resources + + allow { + protocol = "tcp" + ports = ["80", "443"] + } + + target_service_accounts = [ + google_service_account.satapi-lb[0].email, + ] +} + +resource "google_compute_firewall" "satapi-lb-prom" { + name = "${var.name}-prometheus-access-${var.env}" + network = data.google_compute_network.satapi-lb.self_link + project = var.project + count = var.create_resources + + allow { + protocol = "tcp" + ports = ["9100"] + } + + source_service_accounts = [ + var.prom_service_acct, + ] + + target_service_accounts = [ + google_service_account.satapi-lb[0].email, + ] +} + diff --git a/terraform/modules/blc/gcs.tf b/terraform/modules/lb/gcs.tf similarity index 63% rename from terraform/modules/blc/gcs.tf rename to terraform/modules/lb/gcs.tf index c73a82a..b3bf729 100644 --- a/terraform/modules/blc/gcs.tf +++ b/terraform/modules/lb/gcs.tf @@ -1,5 +1,5 @@ # Public bucket (certbot acme-challenge) -resource "google_storage_bucket" "blc-public" { +resource "google_storage_bucket" "satapi-lb-public" { name = "${var.name}-certbot-${var.env}" location = "US" storage_class = "MULTI_REGIONAL" @@ -11,14 +11,14 @@ resource "google_storage_bucket" "blc-public" { } } -resource "google_storage_bucket_acl" "blc-public-acl" { - bucket = google_storage_bucket.blc-public[count.index].name +resource "google_storage_bucket_acl" "satapi-lb-public-acl" { + bucket = google_storage_bucket.satapi-lb-public[count.index].name predefined_acl = "publicread" count = var.create_resources } # Private bucket (server certs) -resource "google_storage_bucket" "blc-private" { +resource "google_storage_bucket" "satapi-lb-private" { name = "${var.name}-certs-${var.env}" location = "US" storage_class = "MULTI_REGIONAL" @@ -30,8 +30,8 @@ resource "google_storage_bucket" "blc-private" { } } -resource "google_storage_bucket_acl" "blc-private-acl" { - bucket = google_storage_bucket.blc-private[count.index].name +resource "google_storage_bucket_acl" "satapi-lb-private-acl" { + bucket = google_storage_bucket.satapi-lb-private[count.index].name predefined_acl = "projectprivate" count = var.create_resources } diff --git a/terraform/modules/lb/iam.tf b/terraform/modules/lb/iam.tf new file mode 100644 index 0000000..1d1d987 --- /dev/null +++ b/terraform/modules/lb/iam.tf @@ -0,0 +1,14 @@ +resource "google_service_account" "satapi-lb" { + account_id = "${var.name}-${var.env}" + display_name = "${var.name}-${var.env}" + project = var.project + count = var.create_resources +} + +resource "google_project_iam_member" "satapi-lb" { + project = var.project + role = "roles/editor" + member = "serviceAccount:${google_service_account.satapi-lb[0].email}" + count = var.create_resources +} + diff --git a/terraform/modules/lb/main.tf b/terraform/modules/lb/main.tf new file mode 100644 index 0000000..3f87a67 --- /dev/null +++ b/terraform/modules/lb/main.tf @@ -0,0 +1,90 @@ +resource "google_compute_region_autoscaler" "satapi-lb" { + name = "${var.name}-autoscaler-${var.env}" + target = google_compute_region_instance_group_manager.satapi-lb[0].self_link + region = var.region + project = var.project + provider = google + count = var.create_resources + + autoscaling_policy { + max_replicas = 2 + min_replicas = 1 + cooldown_period = 60 + + cpu_utilization { + target = 0.8 + } + } +} + +# Instance group & template +resource "google_compute_region_instance_group_manager" "satapi-lb" { + name = "${var.name}-ig-${var.env}" + target_pools = [var.target_pool] + project = var.project + provider = google-beta + count = var.create_resources + + base_instance_name = "${var.name}-ig-${var.env}" + region = var.region + target_size = 1 + + version { + name = "original" + instance_template = google_compute_instance_template.satapi-lb[0].self_link + } + + update_policy { + type = "OPPORTUNISTIC" + minimal_action = "REPLACE" + max_surge_fixed = 3 + max_unavailable_fixed = 0 + min_ready_sec = 60 + } +} + +resource "google_compute_instance_template" "satapi-lb" { + name_prefix = "${var.name}-${var.env}-tmpl-" + description = "This template is used to create ${var.name} ${var.env} instances." + machine_type = var.instance_type + region = var.region + count = var.create_resources + project = var.project + + labels = { + type = "lightning-app" + name = var.name + } + + scheduling { + automatic_restart = true + on_host_maintenance = "MIGRATE" + } + + disk { + source_image = "cos-cloud/cos-stable" + disk_type = "pd-standard" + auto_delete = true + boot = true + disk_size_gb = 20 + } + + network_interface { + network = data.google_compute_network.satapi-lb.self_link + access_config {} + } + + metadata = { + google-logging-enabled = "true" + user-data = data.template_cloudinit_config.satapi-lb[0].rendered + } + + service_account { + email = google_service_account.satapi-lb[0].email + scopes = ["compute-ro", "storage-rw"] + } + + lifecycle { + create_before_destroy = true + } +} diff --git a/terraform/modules/lb/outputs.tf b/terraform/modules/lb/outputs.tf new file mode 100644 index 0000000..360146e --- /dev/null +++ b/terraform/modules/lb/outputs.tf @@ -0,0 +1,3 @@ +output "lb_svc_acct" { + value = google_service_account.satapi-lb[0].email +} diff --git a/terraform/modules/lb/variables.tf b/terraform/modules/lb/variables.tf new file mode 100644 index 0000000..630171e --- /dev/null +++ b/terraform/modules/lb/variables.tf @@ -0,0 +1,72 @@ +variable "project" { + type = string + default = "satellite-api" +} + +variable "create_resources" { + type = string +} + +variable "env" { + type = string +} + +variable "name" { + type = string +} + +variable "network" { + type = string +} + +variable "region" { + type = string +} + +variable "zone" { + type = string +} + +variable "instance_type" { + type = string +} + +variable "host" { + type = string +} + +variable "timeout" { + type = string +} + +variable "public_bucket_url" { + type = string +} + +variable "letsencrypt_email" { + type = string +} + +variable "internal_ip_mainnet" { + type = string +} + +variable "internal_ip_testnet" { + type = string +} + +variable "prom_service_acct" { + type = string +} + +variable "target_pool" { + type = string +} + +variable "node_exporter_docker" { + type = string +} + +variable "certbot_docker" { + type = string +} \ No newline at end of file diff --git a/terraform/modules/tor/main.tf b/terraform/modules/tor/main.tf index 6967d86..c33b060 100644 --- a/terraform/modules/tor/main.tf +++ b/terraform/modules/tor/main.tf @@ -80,4 +80,3 @@ resource "google_compute_instance_template" "tor" { create_before_destroy = true } } - diff --git a/terraform/network.tf b/terraform/network.tf index fbeb671..b828dfd 100644 --- a/terraform/network.tf +++ b/terraform/network.tf @@ -9,7 +9,7 @@ resource "google_compute_address" "lb" { # Forwarding rules resource "google_compute_forwarding_rule" "rule-https" { name = "satellite-api-https-forwarding-rule-${local.env}" - target = google_compute_target_pool.blc-pool[0].self_link + target = google_compute_target_pool.lb-pool[0].self_link port_range = "443" ip_protocol = "TCP" ip_address = google_compute_address.lb[0].address @@ -20,7 +20,7 @@ resource "google_compute_forwarding_rule" "rule-https" { resource "google_compute_forwarding_rule" "rule-http" { name = "satellite-api-http-forwarding-rule-${local.env}" - target = google_compute_target_pool.blc-pool[0].self_link + target = google_compute_target_pool.lb-pool[0].self_link port_range = "80" ip_protocol = "TCP" ip_address = google_compute_address.lb[0].address @@ -29,19 +29,19 @@ resource "google_compute_forwarding_rule" "rule-http" { count = local.create_mainnet } -resource "google_compute_target_pool" "blc-pool" { - name = "satellite-api-target-pool-${local.env}" +resource "google_compute_target_pool" "lb-pool" { + name = "satellite-api-lb-target-pool-${local.env}" region = var.region project = var.project count = local.create_mainnet health_checks = [ - google_compute_http_health_check.blc-health[0].self_link + google_compute_http_health_check.lb-health[0].self_link ] } -resource "google_compute_http_health_check" "blc-health" { - name = "satellite-api-http-health-${local.env}" +resource "google_compute_http_health_check" "lb-health" { + name = "satellite-api-lb-http-health-${local.env}" project = var.project count = local.create_mainnet diff --git a/terraform/outputs.tf b/terraform/outputs.tf index c636f29..526e832 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -7,7 +7,16 @@ output "blc_backend_service_mainnet" { value = module.blc-mainnet.backend_service } +# Internal IP used for proxy_pass-ing to correct instance (mainnet vs testnet) +output "blc_internal_ip_testnet" { + value = module.blc-testnet.internal_ip +} + +# Remote service accounts used for firewall rules output "prom_svc_acct" { value = module.prometheus.prom_svc_acct } +output "lb_svc_acct" { + value = module.lb.lb_svc_acct +} diff --git a/terraform/variables.tf b/terraform/variables.tf index cfc259e..163dae9 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -95,7 +95,7 @@ variable "instance_type" { variable "timeout" { type = string - default = 15 + default = 7200 } variable "prom_service_acct" { @@ -103,6 +103,11 @@ variable "prom_service_acct" { default = "" } +variable "lb_svc_acct" { + type = string + default = "" +} + variable "prom_allowed_source_ip" { type = string default = "" @@ -128,17 +133,17 @@ variable "satellite_api_lb_staging" { default = "" } +variable "internal_ip_mainnet" { + type = string + default = "" +} + +variable "internal_ip_testnet" { + type = string + default = "" +} + # Overwritten by CI -variable "ionosphere_docker" { - type = string - default = "" -} - -variable "ionosphere_sse_docker" { - type = string - default = "" -} - variable "public_bucket_url" { type = string default = "" @@ -149,6 +154,16 @@ variable "letsencrypt_email" { default = "" } +variable "ionosphere_docker" { + type = string + default = "" +} + +variable "ionosphere_sse_docker" { + type = string + default = "" +} + # Less frequently updated images variable "bitcoin_docker" { type = string