From 9d6d8a4d8e0eef78bfee45ee8451b694897cf4e4 Mon Sep 17 00:00:00 2001 From: Rocket Date: Tue, 25 Jun 2024 11:35:26 -0700 Subject: [PATCH] Prepare for integration with infra template (#40) --- README.md | 2 +- app-rails/Makefile | 3 ++ .../app/adapters/auth/cognito_adapter.rb | 26 +++++----- app-rails/config/environments/development.rb | 2 +- app-rails/config/environments/production.rb | 5 ++ app-rails/config/storage.yml | 2 +- ...0240613000011_enable_extension_for_uuid.rb | 9 ---- app-rails/db/schema.rb | 3 +- app-rails/local.env.example | 14 ++++-- docs/app-rails/technical-foundation.md | 7 ++- template-only-docs/Deployment.md | 48 +++++++++++++++++++ 11 files changed, 85 insertions(+), 36 deletions(-) delete mode 100644 app-rails/db/migrate/20240613000011_enable_extension_for_uuid.rb create mode 100644 template-only-docs/Deployment.md diff --git a/README.md b/README.md index 22dc352..f19746d 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ If you have previously installed this template and would like to update your pro 1. Run the [update script](./template-only-bin/update-template) in your project's root directory and pass in the branch, commit hash, or release that you want to update to, followed by the name of your application directory (e.g. `app-rails`). ```bash - curl https://raw.githubusercontent.com/navapbc/template-application-rails/main/template-only-bin/download-and-install-template | bash -s -- + curl https://raw.githubusercontent.com/navapbc/template-application-rails/main/template-only-bin/update-template | bash -s -- ``` This script will: diff --git a/app-rails/Makefile b/app-rails/Makefile index 3b435d4..e8a541e 100644 --- a/app-rails/Makefile +++ b/app-rails/Makefile @@ -144,6 +144,9 @@ db-up: ## Run just the database container db-migrate: ## Run database migrations $(RAILS_RUN_CMD) db:migrate +db-rollback: ## Rollback a database migration + $(RAILS_RUN_CMD) db:rollback + db-test-prepare: ## Prepare the test database $(RAILS_RUN_CMD) db:test:prepare diff --git a/app-rails/app/adapters/auth/cognito_adapter.rb b/app-rails/app/adapters/auth/cognito_adapter.rb index 97de4bb..9ddad83 100644 --- a/app-rails/app/adapters/auth/cognito_adapter.rb +++ b/app-rails/app/adapters/auth/cognito_adapter.rb @@ -14,7 +14,7 @@ def initialize(client: Aws::CognitoIdentityProvider::Client.new) def create_account(email, password) begin response = @client.sign_up( - client_id: ENV["AWS_COGNITO_CLIENT_ID"], + client_id: ENV["COGNITO_CLIENT_ID"], secret_hash: get_secret_hash(email), username: email, password: password, @@ -47,7 +47,7 @@ def create_account(email, password) def forgot_password(email) begin response = @client.forgot_password( - client_id: ENV["AWS_COGNITO_CLIENT_ID"], + client_id: ENV["COGNITO_CLIENT_ID"], secret_hash: get_secret_hash(email), username: email ) @@ -64,7 +64,7 @@ def forgot_password(email) def confirm_forgot_password(email, code, password) begin @client.confirm_forgot_password( - client_id: ENV["AWS_COGNITO_CLIENT_ID"], + client_id: ENV["COGNITO_CLIENT_ID"], secret_hash: get_secret_hash(email), username: email, confirmation_code: code, @@ -84,7 +84,7 @@ def confirm_forgot_password(email, code, password) def change_email(uid, new_email) begin response = @client.admin_update_user_attributes( - user_pool_id: ENV["AWS_COGNITO_USER_POOL_ID"], + user_pool_id: ENV["COGNITO_USER_POOL_ID"], username: uid, user_attributes: [ { @@ -114,8 +114,8 @@ def change_email(uid, new_email) def initiate_auth(email, password) begin response = @client.admin_initiate_auth( - user_pool_id: ENV["AWS_COGNITO_USER_POOL_ID"], - client_id: ENV["AWS_COGNITO_CLIENT_ID"], + user_pool_id: ENV["COGNITO_USER_POOL_ID"], + client_id: ENV["COGNITO_CLIENT_ID"], auth_flow: "ADMIN_USER_PASSWORD_AUTH", auth_parameters: { "USERNAME" => email, @@ -151,7 +151,7 @@ def initiate_auth(email, password) def resend_verification_code(email) begin @client.resend_confirmation_code( - client_id: ENV["AWS_COGNITO_CLIENT_ID"], + client_id: ENV["COGNITO_CLIENT_ID"], secret_hash: get_secret_hash(email), username: email ) @@ -163,8 +163,8 @@ def resend_verification_code(email) def respond_to_auth_challenge(code, challenge = {}) begin response = @client.admin_respond_to_auth_challenge( - client_id: ENV["AWS_COGNITO_CLIENT_ID"], - user_pool_id: ENV["AWS_COGNITO_USER_POOL_ID"], + client_id: ENV["COGNITO_CLIENT_ID"], + user_pool_id: ENV["COGNITO_USER_POOL_ID"], challenge_name: "SOFTWARE_TOKEN_MFA", session: challenge[:session], challenge_responses: { @@ -184,7 +184,7 @@ def respond_to_auth_challenge(code, challenge = {}) def verify_account(email, code) begin @client.confirm_sign_up( - client_id: ENV["AWS_COGNITO_CLIENT_ID"], + client_id: ENV["COGNITO_CLIENT_ID"], secret_hash: get_secret_hash(email), username: email, confirmation_code: code @@ -242,7 +242,7 @@ def verify_software_token(code, access_token) def disable_software_token(uid) begin @client.admin_set_user_mfa_preference( - user_pool_id: ENV["AWS_COGNITO_USER_POOL_ID"], + user_pool_id: ENV["COGNITO_USER_POOL_ID"], username: uid, software_token_mfa_settings: { enabled: false, @@ -265,8 +265,8 @@ def get_auth_result(response) end def get_secret_hash(username) - message = username + ENV["AWS_COGNITO_CLIENT_ID"] - key = ENV["AWS_COGNITO_CLIENT_SECRET"] + message = username + ENV["COGNITO_CLIENT_ID"] + key = ENV["COGNITO_CLIENT_SECRET"] Base64.strict_encode64(OpenSSL::HMAC.digest("sha256", key, message)) end diff --git a/app-rails/config/environments/development.rb b/app-rails/config/environments/development.rb index abaf811..eabb4f5 100644 --- a/app-rails/config/environments/development.rb +++ b/app-rails/config/environments/development.rb @@ -36,7 +36,7 @@ config.cache_store = :null_store end - config.active_storage.service = ENV["AWS_BUCKET_NAME"] ? :amazon : :local + config.active_storage.service = ENV["BUCKET_NAME"] ? :amazon : :local config.action_mailer.delivery_method = ENV["SES_EMAIL"] ? :sesv2 : :letter_opener diff --git a/app-rails/config/environments/production.rb b/app-rails/config/environments/production.rb index 085748c..02416f7 100644 --- a/app-rails/config/environments/production.rb +++ b/app-rails/config/environments/production.rb @@ -54,6 +54,11 @@ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true + # Exclude healthcheck endpoint from force SSL since healthchecks should not go through + # the reverse proxy. + # See https://api.rubyonrails.org/classes/ActionDispatch/SSL.html + config.ssl_options = { redirect: { exclude: -> request { /health/.match?(request.path) } } } + # Log to STDOUT by default config.logger = ActiveSupport::Logger.new(STDOUT) .tap { |logger| logger.formatter = ::Logger::Formatter.new } diff --git a/app-rails/config/storage.yml b/app-rails/config/storage.yml index 2ff2e69..04f68c1 100644 --- a/app-rails/config/storage.yml +++ b/app-rails/config/storage.yml @@ -8,7 +8,7 @@ local: amazon: service: S3 - bucket: <%= ENV.fetch("AWS_BUCKET_NAME") { nil } %> + bucket: <%= ENV.fetch("BUCKET_NAME") { nil } %> # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) diff --git a/app-rails/db/migrate/20240613000011_enable_extension_for_uuid.rb b/app-rails/db/migrate/20240613000011_enable_extension_for_uuid.rb deleted file mode 100644 index 000e013..0000000 --- a/app-rails/db/migrate/20240613000011_enable_extension_for_uuid.rb +++ /dev/null @@ -1,9 +0,0 @@ -class EnableExtensionForUuid < ActiveRecord::Migration[7.1] - def up - enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') - end - - def down - disable_extension 'pgcrypto' if extension_enabled?('pgcrypto') - end -end diff --git a/app-rails/db/schema.rb b/app-rails/db/schema.rb index 1bc0073..709bdc0 100644 --- a/app-rails/db/schema.rb +++ b/app-rails/db/schema.rb @@ -10,9 +10,8 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_06_13_000011) do +ActiveRecord::Schema[7.1].define(version: 2024_04_10_213056) do # These are extensions that must be enabled in order to support this database - enable_extension "pgcrypto" enable_extension "plpgsql" create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| diff --git a/app-rails/local.env.example b/app-rails/local.env.example index 2a40c53..25a2e70 100644 --- a/app-rails/local.env.example +++ b/app-rails/local.env.example @@ -19,16 +19,20 @@ AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION= +############################ +# File storage +############################ + # Uncomment and fill in an AWS S3 bucket name to use S3 for file storage -# AWS_BUCKET_NAME= +# BUCKET_NAME= ############################ # Auth ############################ -AWS_COGNITO_USER_POOL_ID= -AWS_COGNITO_CLIENT_ID= -AWS_COGNITO_CLIENT_SECRET= +COGNITO_USER_POOL_ID= +COGNITO_CLIENT_ID= +COGNITO_CLIENT_SECRET= ############################ # Database @@ -37,4 +41,4 @@ AWS_COGNITO_CLIENT_SECRET= DB_HOST=127.0.0.1 DB_NAME=app_rails DB_USER=app_rails -DB_PASSWORD=secret123 \ No newline at end of file +DB_PASSWORD=secret123 diff --git a/docs/app-rails/technical-foundation.md b/docs/app-rails/technical-foundation.md index 8198d83..a017072 100644 --- a/docs/app-rails/technical-foundation.md +++ b/docs/app-rails/technical-foundation.md @@ -40,7 +40,6 @@ We have are using [UUIDs for primary keys](https://guides.rubyonrails.org/active - ⚠️ Using ActiveRecord functions like `Foo.first` and `Foo.last` have unreliable results - Generating new models or scaffolds requires passing the `--primary-key-type=uuid` flag. For instance, `make rails-generate GENERATE_COMMAND="model Foo --primary-key-type=uuid"` -- `pgcrypto` is a required dependency #### Enums @@ -61,9 +60,9 @@ To preview email views in the browser, visit: `/rails/mailers` To test AWS SES email sending locally: -1. Set the `AWS_*` env var in your `.env` file -1. Set the `SES_EMAIL` env var to a verified sending identity -1. Restart the server +1. Set the "AWS services" environment variables in your `.env` file. +1. Add an `SES_EMAIL` environment variable to a verified sending identity. +1. Restart the server. ### 🎭 Authentication diff --git a/template-only-docs/Deployment.md b/template-only-docs/Deployment.md new file mode 100644 index 0000000..771be87 --- /dev/null +++ b/template-only-docs/Deployment.md @@ -0,0 +1,48 @@ +# Deployment to cloud environments + +## Requirements + +- External service access: The application must be able to make network calls to the public internet. +- AWS Cognito: The application comes with AWS Cognito enabled by default, so AWS Cognito must be set up before deploying the application. +- Custom domain name with HTTPS support: AWS Cognito requires HTTPS for callback urls to function. +- Access to write to temporary directory: Rails needs to be able to write out temporary files. +- Environment variables: You must provide the application with the environment variables listed in [local.env.example](/app-rails/local.env.example). +- Secrets: You must provide the application with a secret `SECRET_KEY_BASE`. For more information, see the [Rails Guide on Security](https://guides.rubyonrails.org/v7.1/security.html#environmental-security). + +## Deploying using the Platform Infrastructure Template + +*Note: The following will be true once https://github.com/navapbc/template-infra/pull/650 is merged.* + +This template can be deployed using the [Nava Platform Infrastructure Template](https://github.com/navapbc/template-infra). Using the infrastructure template will handle creating and configuring all of the resources required in AWS. + +While following the [infrastructure template installation instructions](https://github.com/navapbc/template-infra?tab=readme-ov-file#installation) and [setup instructions](https://github.com/navapbc/template-infra/blob/main/infra/README.md), use the following configuration: + +1. Rename `/infra/app` to `/infra/` so that it matches the directory name of your application. By default, this is `app-rails`. +1. In `/infra//app-config/main.tf`: + 1. Set `has_external_non_aws_service` to `true`. + 2. Set `enable_identity_provider` to `true`. +1. In `/infra//app-config/.tf`: + 1. Set the `domain_name`. + 2. Set `enable_https` to `true`. + 3. Set `enable_command_execution` to `true`: This is necessary temporarily until a temporary file system can be enabled. Otherwise, ECS will run with read-only root filesystem, which will cause rails to error. +1. In `/infra//app-config/env-config/environment-variables.tf`: + 1. Add an entry to `secrets`: + ```terraform + SECRET_KEY_BASE = { + manage_method = "generated" + secret_store_name = "/${var.app_name}-${var.environment}/service/rails-secret-key-base" + } + ``` +1. In `/infra/networks/main.tf`: + 1. Modify the `app_config` module to change the path to match the directory name of your application. By default, this is `app-rails`. + ```terraform + module "app_config" { + source = "..//app-config" + } + ``` +1. Follow the infrastructure template instructions to configure [custom domains](https://github.com/navapbc/template-infra/blob/main/docs/infra/set-up-custom-domains.md) and [https support](https://github.com/navapbc/template-infra/blob/main/docs/infra/https-support.md). + +## Deploying using another method + +- AWS Cognito requires a lot of configuration to work correctly. See the Nava Platform infrastructure template for example configuration. +- If you are deploying using AWS ECS, but don't want to use the Platform infrastructure template, pass in environment variables and secrets using the ECS task definition. Use the `environment` key for environment variables and the `secrets` key with `valueFrom` for secrets.