diff --git a/.env.example b/.env.example index a6fc918b..3a421b45 100644 --- a/.env.example +++ b/.env.example @@ -26,8 +26,7 @@ NOTIFICATIONS_EMAIL_USER="no-reply@senecacollege.ca" SMTP_PORT=1025 # SSO Config -HOSTNAME=http://localhost:8080 +# NOTE: we don't use localhost here due to DNS issues between Docker and host +HOSTNAME=http://host.docker.internal:8080 # The SimpleSAML IDP's XML metadata SAML_IDP_METADATA_PATH=config/idp-metadata-dev.xml -# Our apps's SP Entity ID, which is also the URL to our metadata. -SAML_ENTITY_ID=http://host.docker.internal:8080/sp diff --git a/DEPLOY.md b/DEPLOY.md index 3af5b9aa..5430dfd9 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -17,18 +17,20 @@ A number of environment variables and Docker secrets are required at runtime. The following configuration values must be set via environment variables. -| Variable Name | Description | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PORT` | The server runs on port `8080` by default | -| `LOG_LEVEL` | The log level to use for log messages. One of `error`, `debug`, `info`, etc. See [Winston docs](https://github.com/winstonjs/winston#logging-levels). Defaults to `info` | -| `ROOT_DOMAIN` | The DNS root domain for the hosted zone (e.g., `starchart.com`) | -| `AWS_ROUTE53_HOSTED_ZONE_ID` | The existing Amazon Route53 Hosted Zone ID to use (e.g., `Z23ABC4XYZL05B`) | -| `NOTIFICATIONS_EMAIL_USER` | The email address from which notifications are sent | -| `SMTP_PORT` | The port to use for the SMTP server. Defaults to `587` in production (using `smtp.office365.com`) and `1025` in development (using ([MailHog](https://github.com/mailhog/MailHog)) | -| `LETS_ENCRYPT_ACCOUNT_EMAIL` | The email address to use for the app's [single Let's Encrypt account](https://letsencrypt.org/docs/integration-guide/#one-account-or-many) | -| `REDIS_URL` | The Redis server to use for the worker queues. Defaults to `redis://redis:6379` in production and `localhost:6379` in development. | -| `SAML_IDP_METADATA_PATH` | The file path of the SAML Identify Provider (IdP)'s metadata XML. We store various XML files in `config/` and use `config/idp-metadata-dev.xml` by default. | -| `SECRETS_OVERRIDE` | In development, to override the Docker secrets | +| Variable Name | Description | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `HOSTNAME` | The hostname of the server (e.g., `https://mycustomdomain.senecacollege.ca`). NOTE: when running in development, use `http://host.docker.internal:8080` vs. `http://localhost`, so that Docker DNS resolution works between the login container and host. | +| `PORT` | The server runs on port `8080` by default | +| `LOG_LEVEL` | The log level to use for log messages. One of `error`, `debug`, `info`, etc. See [Winston docs](https://github.com/winstonjs/winston#logging-levels). Defaults to `info` | +| `ROOT_DOMAIN` | The DNS root domain for the hosted zone (e.g., `starchart.com`) | +| `AWS_ROUTE53_HOSTED_ZONE_ID` | The existing Amazon Route53 Hosted Zone ID to use (e.g., `Z23ABC4XYZL05B`) | +| `NOTIFICATIONS_EMAIL_USER` | The email address from which notifications are sent | +| `SMTP_PORT` | The port to use for the SMTP server. Defaults to `587` in production (using `smtp.office365.com`) and `1025` in development (using ([MailHog](https://github.com/mailhog/MailHog)) | +| `LETS_ENCRYPT_ACCOUNT_EMAIL` | The email address to use for the app's [single Let's Encrypt account](https://letsencrypt.org/docs/integration-guide/#one-account-or-many) | +| `REDIS_URL` | The Redis server to use for the worker queues. Defaults to `redis://redis:6379` in production and `localhost:6379` in development. | +| `SAML_IDP_METADATA_PATH` | The file path of the SAML Identify Provider (IdP)'s metadata XML. We store various XML files in `config/` and use `config/idp-metadata-dev.xml` by default in development. | +| `SECRETS_OVERRIDE` | In development, to override the Docker secrets | +| `DATABASE_SETUP` | In order to sync the Prisma schema with MySQL, set `DATABASE_SETUP=1` when running the app | ### Secrets diff --git a/app/lib/saml.server.ts b/app/lib/saml.server.ts index 6512fa0b..73e59225 100644 --- a/app/lib/saml.server.ts +++ b/app/lib/saml.server.ts @@ -18,21 +18,30 @@ const idp = samlify.IdentityProvider({ metadata: xml, }); +const { HOSTNAME } = process.env; + +/** + * We require the hostname or can't configure SAML. + */ +if (!HOSTNAME) { + throw new Error('HOSTNAME environment variable is missing'); +} + // Here we configure the service provider: https://samlify.js.org/#/sp-configuration const sp = samlify.ServiceProvider({ - entityID: process.env.SAML_ENTITY_ID, + entityID: new URL('/sp', HOSTNAME).href, nameIDFormat: ['urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'], wantAssertionsSigned: true, assertionConsumerService: [ { Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - Location: process.env.HOSTNAME + '/login/callback', + Location: new URL('/login/callback', HOSTNAME).href, }, ], singleLogoutService: [ { Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - Location: process.env.HOSTNAME + '/logout/callback', + Location: new URL('/logout/callback', HOSTNAME).href, }, ], }); diff --git a/docker-staging.yml b/docker-staging.yml new file mode 100644 index 00000000..525eec5b --- /dev/null +++ b/docker-staging.yml @@ -0,0 +1,67 @@ +# Stack Definition for Staging +services: + # Redis is used to back our worker queues. It is not exposed. + redis: + image: redis:7.0.9-alpine3.17@sha256:8201775852e31262823ac8da9d76d0c8f36583f1a028b4800c35fc319c75289f + restart: unless-stopped + volumes: + - redis-data:/data + deploy: + placement: + # We run the redis instance on the manager node only + constraints: [node.role == manager] + + mycustomdomain: + # Staging runs the most recent commit on the main branch + image: ghcr.io/developingspace/starchart:main + restart: unless-stopped + depends_on: + - redis + ports: + - 8080:8080 + environment: + - HOSTNAME=https://mycustomdomain-dev.senecacollege.ca + - AWS_ROUTE53_HOSTED_ZONE_ID=Z0228625ICAL609E0BBT + - LETS_ENCRYPT_ACCOUNT_EMAIL=mycustomdomain-dev@senecacollege.ca + - LETS_ENCRYPT_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory + - LOG_LEVEL=info + - NODE_ENV=production + - NOTIFICATIONS_EMAIL_USER=mycustomdomain-dev@senecacollege.ca + - PORT=8080 + - REDIS_URL=redis://redis:6379 + - ROOT_DOMAIN=_stage_.mystudentproject.ca + secrets: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - DATABASE_URL + - LETS_ENCRYPT_ACCOUNT_PRIVATE_KEY_PEM + - NOTIFICATIONS_USERNAME + - NOTIFICATIONS_PASSWORD + - SESSION_SECRET + deploy: + mode: replicated + replicas: 2 + update_config: + # Only update 1 instance at a time, not all at once (rolling-update) + parallelism: 1 + # If the update fails, rollback to last-known-good + failure_action: rollback + +secrets: + AWS_ACCESS_KEY_ID: + external: true + AWS_SECRET_ACCESS_KEY: + external: true + DATABASE_URL: + external: true + LETS_ENCRYPT_ACCOUNT_PRIVATE_KEY_PEM: + external: true + NOTIFICATIONS_USERNAME: + external: true + NOTIFICATIONS_PASSWORD: + external: true + SESSION_SECRET: + external: true + +volumes: + redis-data: