diff --git a/config/TLS-example/README.md b/config/TLS-example/README.md new file mode 100644 index 0000000..8998363 --- /dev/null +++ b/config/TLS-example/README.md @@ -0,0 +1,116 @@ +# TLS example + +This folder contains example files that can be used as a base when deploying a more `production like` setup. + +**Note** this is not usable as is, manual intervention is required. + +## Configuration example + +### config.yaml file + +In the `config.yaml` the following entries must be set: + +```yaml +archive: + s3url: + accesskey: + secretkey: + +inbox: + s3url: + accesskey: + secretkey: + +c4gh: + passphrase: + +elixir: + id: + secret: + redirectUrl: + +session: + domain: + +s3inbox: +``` + +The `config.yaml` contains comments to help setting the correct value + +### env file + +The following entries in the env file needs to be set: + +- `C4GH_KEYPATH`: path to the crypt4gh private key. +- `rabbitmq_MQ_PASSWORD`: admin password to the RabbitMQ server +- `rabbitmq_MQ_USER`: username for the RabbitMQ admin +- `postgres_POSTGRES_PASSWORD`: password for the `postgres` user + +After which the file needs to be renamed with a dot prefix (`.env`) + +#### service credentials + +These credentials can be created manually in Postgres/RabbitMQ, or with the use of the `make_credentials` script once the RabbitMQ and Postgres containers are running. The usernames should be the same as the `container_name` in the docker-compose file + +##### RabbitMQ + +- finalize_BROKER_PASSWORD +- ingest_BROKER_PASSWORD +- mapper_BROKER_PASSWORD +- verify_BROKER_PASSWORD +- inbox_BROKER_PASSWORD + +##### Postgres + +- download_DB_PASSWORD +- finalize_DB_PASSWORD +- ingest_DB_PASSWORD +- mapper_DB_PASSWORD +- verify_DB_PASSWORD +- inbox_DB_PASSWORD + +### iss.json file + +In the `iss.json` file a block for the publicly available endpoint for REMS should to be added. + +### certificates + +All containers except `auth` needs certificates with DNS entries matching the `container_name` in the docker-compose file. +These certificate files should adhere to the [cert-manager](https://cert-manager.io/) naming convention (tls.crt, tls.key, ca.crt). + +The following containers also needs external certificates generated by a public CA like [Let's Encrypt](https://letsencrypt.org/): + +- Auth +- Download +- S3inbox + +These certificate files should adhere to Let's Encrypts naming schema (fullchain.pem, privkey.pem). + +All certificates for a service should be placed in a folder with the same name as the `container_name` and be placed next to the docker-compose file. +Permissions for the private key needs to be set at `0400` and the owner set as shown below: + +- rabbitmq - `100:101` +- postgres - `70:70` +- all others - `65534:65534` + +If automatic certificate rotation is done through cron it is easier to set the owner to root and group based on the service with the permissions to `0640` for all files in each folder. + +- rabbitmq - `0:101` +- postgres - `0:70` +- all others - `0:65534` + +## Bootstrapping + +First The RabbitMQ and Postgres servers needs to be started. + +```cmd +docker compose up -d postgres rabbitmq +``` + +When they are running with status `healthy` credentials for the services can be created, either manually or by executing the `make_credentials` script. + +Once the credentials have been created the rest of the services can be started. + +```cmd +docker compose up -d +``` diff --git a/config/TLS-example/config.yaml b/config/TLS-example/config.yaml new file mode 100644 index 0000000..87bac55 --- /dev/null +++ b/config/TLS-example/config.yaml @@ -0,0 +1,105 @@ +app: # this is for download + host: "0.0.0.0" + port: "8443" + servercert: "/certificates/fullchain.pem" + serverkey: "/certificates/privkey.pem" + +archive: + type: "s3" + url: "" +# port: only needed if port is not 80 or 443 + accesskey: "" + secretkey: "" + bucket: "archive" + chunksize: 32 + +broker: + host: "rabbitmq" + port: 5671 # This is the default SSL port + # user: "" # dont set, this should be unique for each service + # password: "" # dont set, this should be unique for each service + vhost: "sda" + # queue: "" # dont set, this is unique for each service + exchange: "sda" + # routingKey: "" # dont set, this is unique for each service + routingError: "error" + ssl: "true" + cacert: /certificates/ca.crt # if certificates are signed by an private PKI + # verifyPeer: "false" +# If verifyPeer is true clientKey and clientCert needs to be set + # clientCert: /certificates/tls.crt + # clientKey: /certificates/tls.key + +c4gh: + passphrase: "" + filepath: "/c4gh/gdi.sec.pem" + +db: + host: "postgres" + port: 5432 + # user: "" # dont set, this should be unique for each service + # password: "" # dont set, this should be unique for each service + database: "sda" + sslmode: "verify-ca" + cacert: /certificates/ca.crt # if certificates are signed by an private PKI + clientCert: /certificates/tls.crt + clientKey: /certificates/tls.key + + +elixir: # used by the auth service + id: "" # LS AAI client ID + secret: "" # LS AAI client secret + provider: "https://login.elixir-czech.org/oidc/" + jwkpath: "jwks" + redirectUrl: "" # public url to the auth endpoint + +inbox: + type: "s3" + url: "" +# port: only needed if port is not 80 or 443 + accesskey: "" + secretkey: "" + bucket: "inbox" + chunksize: 32 + +log: + level: "info" + format: "json" + +oidc: + configuration: + url: "https://login.elixir-czech.org/oidc/.well-known/openid-configuration" + trusted: + iss: "/iss.json" + +schema: + type: isolated + +server: + jwtpubkeyurl: "https://login.elixir-czech.org/oidc/jwks" + cert: "/certificates/fullchain.pem" + key: "/certificates/privkey.pem" + +session: + # session key expiration time in seconds + # default value = -1 for disabled state + # a positive integer enables sessions + # a negative integer disables sessions + expiration: 28800 + # domain name must be set to the hostname (FQDN) of the service + domain: "" + # session cookie Secure value, if true, TLS must be active + # default value = true + secure: true + # session cookie HttpOnly value, if true, TLS must be active + # default value = true + httponly: true + # name of session cookie + # default value = sda_session_key + name: "sda_session_key" + +# these are used by the auth container +s3inbox: "" # public URL to the s3inbox +resignjwt: "false" +infoText: "About GDI" +infoUrl: "https://gdi.onemilliongenomes.eu/" # or this can be a national site where information about the available datasets can be found. \ No newline at end of file diff --git a/config/TLS-example/docker-compose.yml b/config/TLS-example/docker-compose.yml new file mode 100644 index 0000000..f6a989e --- /dev/null +++ b/config/TLS-example/docker-compose.yml @@ -0,0 +1,221 @@ +services: + auth: + container_name: auth + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156-auth + volumes: + - ./config.yaml:/config.yaml + - ./auth:/certificates/ + ports: + - 8080:8080 + networks: + - public + restart: always + + rabbitmq: + container_name: rabbitmq + environment: + - RABBITMQ_DEFAULT_PASS=${rabbitmq_MQ_PASSWORD} + - RABBITMQ_DEFAULT_USER=${rabbitmq_MQ_USER} + - RABBITMQ_SERVER_CERT=/certificates/tls.crt + - RABBITMQ_SERVER_KEY=/certificates/tls.key + # - RABBITMQ_SERVER_CACERT=/certificates/ca.crt # uncomment if certificate is signed by a private PKI and "RABBITMQ_SERVER_VERIFY" is set + # - RABBITMQ_SERVER_VERIFY="verify_peer" # Require the clients to supply valid TLS certificates + healthcheck: + test: + [ + "CMD", + "sh", + "-c", + "rabbitmq-diagnostics -q check_running && rabbitmq-diagnostics -q check_local_alarms" + ] + interval: 5s + timeout: 20s + retries: 20 + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156-rabbitmq + networks: + - secure + ports: + - 15671:15671 # TLS port for management UI + restart: always + volumes: + - mqdata:/var/lib/rabbitmq + - ./rabbitmq:/certificates + + postgres: + container_name: postgres + environment: + - POSTGRES_PASSWORD=${postgres_POSTGRES_PASSWORD} + - POSTGRES_SERVER_CERT=/certificates/tls.crt + - POSTGRES_SERVER_KEY=/certificates/tls.key + - POSTGRES_SERVER_CACERT=/certificates/ca.crt # needed if certificate is signed by a private PKI + healthcheck: + test: + [ + "CMD", + "pg_isready", + "-h", + "localhost", + "-U", + "postgres", + "-d", + "sda" + ] + interval: 5s + timeout: 20s + retries: 20 + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156-postgres + networks: + - secure + restart: always + volumes: + - pgdata:/var/lib/postgresql/data + - ./postgres:/certificates + + download: + command: sda-download + container_name: download + depends_on: + postgres: + condition: service_healthy + environment: + - DB_PASSWORD=${download_DB_PASSWORD} + - DB_USER=download + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156-download + networks: + - public + - secure + ports: + - 8443:8443 + restart: always + volumes: + - ./config.yaml:/config.yaml + - ./iss.json:/iss.json + - ${C4GH_KEYPATH}:/c4gh/gdi.sec.pem + - ./download:/certificates + + ## data ingest pipeline + finalize: + command: sda-finalize + container_name: finalize + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + environment: + - BROKER_PASSWORD=${finalize_BROKER_PASSWORD} + - BROKER_QUEUE=accession + - BROKER_ROUTINGKEY=completed + - BROKER_USER=finalize + - DB_PASSWORD=${finalize_DB_PASSWORD} + - DB_USER=finalize + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156 + networks: + - secure + restart: always + volumes: + - ./config.yaml:/config.yaml + - ./finalize:/certificates + + ingest: + command: sda-ingest + container_name: ingest + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + environment: + - BROKER_PASSWORD=${ingest_BROKER_PASSWORD} + - BROKER_QUEUE=ingest + - BROKER_ROUTINGKEY=archived + - BROKER_USER=ingest + - DB_PASSWORD=${ingest_DB_PASSWORD} + - DB_USER=ingest + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156 + networks: + - secure + restart: always + volumes: + - ./config.yaml:/config.yaml + - ${C4GH_KEYPATH}:/c4gh/gdi.sec.pem + - ./ingest:/certificates + + mapper: + command: sda-mapper + container_name: mapper + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + environment: + - BROKER_PASSWORD=${mapper_BROKER_PASSWORD} + - BROKER_QUEUE=mappings + - BROKER_USER=mapper + - DB_PASSWORD=${mapper_DB_PASSWORD} + - DB_USER=mapper + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156 + networks: + - secure + restart: always + volumes: + - ./config.yaml:/config.yaml + - ./mapper:/certificates + + verify: + command: sda-verify + container_name: verify + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + environment: + - BROKER_PASSWORD=${verify_BROKER_PASSWORD} + - BROKER_QUEUE=archived + - BROKER_ROUTINGKEY=verified + - BROKER_USER=verify + - DB_PASSWORD=${verify_DB_PASSWORD} + - DB_USER=verify + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156 + networks: + - secure + restart: always + volumes: + - ./config.yaml:/config.yaml + - ${C4GH_KEYPATH}:/c4gh/gdi.sec.pem + - ./verify:/certificates + + s3inbox: + command: sda-s3inbox + container_name: s3inbox + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + environment: + - BROKER_PASSWORD=${s3inbox_BROKER_PASSWORD} + - BROKER_ROUTINGKEY=inbox + - BROKER_USER=inbox + - DB_PASSWORD=${s3inbox_DB_PASSWORD} + - DB_USER=inbox + image: ghcr.io/neicnordic/sensitive-data-archive:v0.0.156 + networks: + - public + - secure + ports: + - "8000:8000" + restart: always + volumes: + - ./config.yaml:/config.yaml + - ./s3inbox:/certificates + +volumes: + mqdata: + pgdata: + +networks: + public: # this network is where external traffic is routed in/out + secure: # this network is only for container to container communication \ No newline at end of file diff --git a/config/TLS-example/env b/config/TLS-example/env new file mode 100644 index 0000000..939acf6 --- /dev/null +++ b/config/TLS-example/env @@ -0,0 +1,27 @@ +# crypth4gh private key +C4GH_KEYPATH= + +### services ### + +# rabbitmq +rabbitmq_MQ_PASSWORD= +rabbitmq_MQ_USER= + +# postgres +postgres_POSTGRES_PASSWORD= + +# These credentials can be created manually +# or with the use of the make_credentials script +# once the RabbitMQ and Postgres containers are running + +download_DB_PASSWORD= +finalize_BROKER_PASSWORD= +finalize_DB_PASSWORD= +ingest_BROKER_PASSWORD= +ingest_DB_PASSWORD= +mapper_BROKER_PASSWORD= +mapper_DB_PASSWORD= +verify_BROKER_PASSWORD= +verify_DB_PASSWORD= +inbox_BROKER_PASSWORD= +inbox_DB_PASSWORD= diff --git a/config/TLS-example/iss.json b/config/TLS-example/iss.json new file mode 100644 index 0000000..3a0d2c9 --- /dev/null +++ b/config/TLS-example/iss.json @@ -0,0 +1,6 @@ +[ + { + "iss": "https://login.elixir-czech.org/oidc/", + "jku": "https://login.elixir-czech.org/oidc/jwk" + } +] \ No newline at end of file diff --git a/config/TLS-example/make_credentials b/config/TLS-example/make_credentials new file mode 100644 index 0000000..afcac0b --- /dev/null +++ b/config/TLS-example/make_credentials @@ -0,0 +1,37 @@ +#!/bin/sh + +if [ -z "$PGPASSWORD" ]; then + echo "PGPASSWORD must be set" + exit 1 +fi + +if [ -z "$PGSSLCERT" ] || [ -z "$PGSSLKEY" ] ||[ -z "$PGSSLROOTCERT" ]; then + echo "PGSSLCERT, PGSSLKEY and PGSSLROOTCERT must be set" + exit 1 +fi + +if [ -z "$MQUSER" ] || [ -z "$MQPASSWORD" ]; then + echo "MQUSER and MQPASSWORD must be set" + exit 1 +fi + +for CMD in curl jq psql; do + command -v "$CMD" > /dev/null || (echo "$CMD not found" && exit 1) +done + +export PGSSLMODE=verify-ca + +for n in download finalize inbox ingest mapper verify; do + echo "creating credentials for: $n" + ## password and permissions for MQ + mqpass=$(head -c 12 /dev/urandom | base64 | tr -d '\+') + body_data=$(jq -n -c --arg password "$mqpass" --arg tags none '$ARGS.named') + curl -s --cacert "$PGSSLROOTCERT" -u "$MQUSER:$MQPASSWORD" -X PUT "http://localhost:15671/api/users/$n" -H "content-type:application/json" -d "${body_data}" + curl -s --cacert "$PGSSLROOTCERT" -u "$MQUSER:$MQPASSWORD" -X PUT "http://localhost:15671/api/permissions/sda/$n" -H "content-type:application/json" -d '{"configure":"","write":"sda","read":".*"}' + sed -i s/"$n"_BROKER_PASSWORD=.*/"$n"_BROKER_PASSWORD="$mqpass"/ .env + + ## password and permissions for DB + dbpass=$(head -c 12 /dev/urandom | base64 | tr -d '\+') + psql -U postgres -h localhost -d sda -c "ALTER ROLE $n LOGIN PASSWORD '$dbpass';" + sed -i s/"$n"_DB_PASSWORD=.*/"$n"_DB_PASSWORD="$dbpass"/ .env +done \ No newline at end of file