├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── story.md ├── pull_request_template.md └── workflows │ └── main.yml ├── .gitignore ├── .rspec ├── .standard.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── SECURITY.md ├── bin ├── console └── setup ├── exe └── rails_template_18f ├── lib ├── generators │ └── rails_template18f │ │ ├── active_storage │ │ ├── active_storage_generator.rb │ │ └── templates │ │ │ ├── app │ │ │ ├── jobs │ │ │ │ └── file_scan_job.rb │ │ │ └── models │ │ │ │ └── file_upload.rb │ │ │ ├── doc │ │ │ └── adr │ │ │ │ └── clamav.md.tt │ │ │ ├── oscal │ │ │ └── component-definitions │ │ │ │ └── active_storage │ │ │ │ └── component-definition.json │ │ │ └── spec │ │ │ ├── jobs │ │ │ └── file_scan_job_spec.rb │ │ │ └── models │ │ │ └── file_upload_spec.rb │ │ ├── auditree │ │ ├── auditree_generator.rb │ │ └── templates │ │ │ ├── bin │ │ │ └── auditree.tt │ │ │ └── github │ │ │ ├── actions │ │ │ └── auditree-cmd │ │ │ │ └── action.yml.tt │ │ │ └── workflows │ │ │ └── auditree-validation.yml.tt │ │ ├── circleci │ │ ├── circleci_generator.rb │ │ └── templates │ │ │ ├── Dockerfile.ci.tt │ │ │ ├── bin │ │ │ └── ci-server-start │ │ │ ├── circleci │ │ │ └── config.yml.tt │ │ │ ├── docker-compose.ci.yml │ │ │ └── oscal │ │ │ └── component-definitions │ │ │ └── circleci │ │ │ └── component-definition.json.tt │ │ ├── cloud_gov_config │ │ ├── cloud_gov_config_generator.rb │ │ └── templates │ │ │ ├── app │ │ │ └── models │ │ │ │ └── cloud_gov_config.rb │ │ │ └── spec │ │ │ └── models │ │ │ └── cloud_gov_config_spec.rb │ │ ├── dap │ │ └── dap_generator.rb │ │ ├── github_actions │ │ ├── github_actions_generator.rb │ │ └── templates │ │ │ ├── github │ │ │ ├── actions │ │ │ │ ├── compile-assets │ │ │ │ │ └── action.yml │ │ │ │ ├── run-server │ │ │ │ │ └── action.yml │ │ │ │ ├── setup-languages │ │ │ │ │ └── action.yml.tt │ │ │ │ └── setup-project │ │ │ │ │ └── action.yml.tt │ │ │ ├── dependabot.yml.tt │ │ │ └── workflows │ │ │ │ ├── assemble-ssp.yml.tt │ │ │ │ ├── brakeman-analysis.yml │ │ │ │ ├── dependency-scans.yml │ │ │ │ ├── deploy-production.yml │ │ │ │ ├── deploy-staging.yml │ │ │ │ ├── owasp-daily-scan.yml.tt │ │ │ │ ├── owasp-scan.yml.tt │ │ │ │ ├── pa11y.yml.tt │ │ │ │ ├── rspec.yml.tt │ │ │ │ ├── terraform-production.yml │ │ │ │ ├── terraform-staging.yml │ │ │ │ └── validate-ssp.yml │ │ │ └── oscal │ │ │ └── component-definitions │ │ │ └── github_actions │ │ │ └── component-definition.json.tt │ │ ├── gitlab_ci │ │ ├── gitlab_ci_generator.rb │ │ └── templates │ │ │ ├── gitlab-ci.yml.tt │ │ │ └── gitlab │ │ │ ├── node.yml.tt │ │ │ ├── rails.yml │ │ │ ├── ruby.yml │ │ │ └── terraform.yml │ │ ├── i18n │ │ ├── i18n_generator.rb │ │ └── templates │ │ │ └── config │ │ │ └── locales │ │ │ ├── en.yml.tt │ │ │ ├── es.yml │ │ │ ├── fr.yml │ │ │ └── zh.yml │ │ ├── i18n_js │ │ ├── i18n_js_generator.rb │ │ └── templates │ │ │ ├── app │ │ │ └── javascript │ │ │ │ └── i18n │ │ │ │ └── index.js │ │ │ ├── config │ │ │ ├── i18n-js.yml │ │ │ └── initializers │ │ │ │ └── i18n_js.rb │ │ │ └── lib │ │ │ └── tasks │ │ │ └── i18n.rake │ │ ├── newrelic │ │ ├── newrelic_generator.rb │ │ └── templates │ │ │ ├── config │ │ │ └── newrelic.yml.tt │ │ │ └── oscal │ │ │ └── component-definitions │ │ │ └── newrelic │ │ │ └── component-definition.json.tt │ │ ├── oscal │ │ ├── oscal_generator.rb │ │ └── templates │ │ │ ├── bin │ │ │ └── trestle.tt │ │ │ ├── doc │ │ │ └── compliance │ │ │ │ └── oscal │ │ │ │ └── trestle-config.yaml.tt │ │ │ └── github │ │ │ └── actions │ │ │ └── trestle-cmd │ │ │ └── action.yml.tt │ │ ├── public_egress │ │ └── public_egress_generator.rb │ │ ├── rails_erd │ │ ├── rails_erd_generator.rb │ │ └── templates │ │ │ └── erdconfig │ │ ├── sidekiq │ │ ├── sidekiq_generator.rb │ │ └── templates │ │ │ └── config │ │ │ └── initializers │ │ │ └── redis.rb │ │ └── terraform │ │ ├── templates │ │ ├── gitlab_bootstrap │ │ │ ├── apply.sh │ │ │ ├── bot_secrets.tftpl │ │ │ ├── main.tf.tt │ │ │ ├── setup_shadowenv.sh │ │ │ └── users.auto.tfvars │ │ ├── s3_bootstrap │ │ │ ├── common │ │ │ │ ├── apply.sh │ │ │ │ ├── templates │ │ │ │ │ ├── backend_config.tftpl │ │ │ │ │ └── bot_secrets.tftpl │ │ │ │ └── users.auto.tfvars │ │ │ ├── full │ │ │ │ ├── imports.tf.tftpl │ │ │ │ └── main.tf.tt │ │ │ └── sandbox │ │ │ │ ├── imports.tf.tftpl │ │ │ │ └── main.tf.tt │ │ └── terraform │ │ │ ├── .shadowenv.d │ │ │ └── .gitignore │ │ │ ├── README.md.tt │ │ │ ├── app.tf.tt │ │ │ ├── main.tf.tt │ │ │ ├── production.tfvars.tt │ │ │ ├── providers.tf.tt │ │ │ ├── staging.tfvars.tt │ │ │ ├── terraform.sh.tt │ │ │ └── variables.tf.tt │ │ └── terraform_generator.rb ├── rails_template18f │ ├── generators.rb │ ├── generators │ │ ├── base.rb │ │ ├── cloud_gov_options.rb │ │ └── cloud_gov_parsing.rb │ └── version.rb └── rails_template_18f.rb ├── rails-template-18f.gemspec ├── railsrc ├── railsrc-hotwire ├── spec ├── generators │ └── rails_template18f │ │ ├── active_storage │ │ └── active_storage_generator_spec.rb │ │ ├── auditree │ │ └── auditree_generator_spec.rb │ │ ├── circleci │ │ └── circleci_generator_spec.rb │ │ ├── cloud_gov_config │ │ └── cloud_gov_config_generator_spec.rb │ │ ├── dap │ │ └── dap_generator_spec.rb │ │ ├── github_actions │ │ └── github_actions_generator_spec.rb │ │ ├── gitlab_ci │ │ └── gitlab_ci_generator_spec.rb │ │ ├── i18n │ │ └── i18n_generator_spec.rb │ │ ├── i18n_js │ │ └── i18n_js_generator_spec.rb │ │ ├── newrelic │ │ └── newrelic_generator_spec.rb │ │ ├── oscal │ │ └── oscal_generator_spec.rb │ │ ├── public_egress │ │ └── public_egress_generator_spec.rb │ │ ├── rails_erd │ │ └── rails_erd_generator_spec.rb │ │ ├── sidekiq │ │ └── sidekiq_generator_spec.rb │ │ └── terraform │ │ └── terraform_generator_spec.rb ├── rails_template_18f_spec.rb ├── spec_helper.rb └── support │ ├── generators.rb │ └── test_app_template.rb ├── template.rb └── templates ├── Brewfile ├── README.md.tt ├── app ├── assets │ └── stylesheets │ │ ├── uswds-components.scss │ │ ├── uswds-overrides │ │ ├── _index.scss │ │ ├── _override-usa-banner.scss │ │ └── _override-usa-language-selector.scss │ │ └── uswds-settings.scss └── views │ └── application │ ├── _banner_lock_icon.html.erb │ ├── _demo_site_banner.html.erb │ ├── _header.html.erb │ ├── _language_selector.html.erb │ └── _usa_banner.html.erb ├── bin ├── ops │ ├── create_service_account.sh.tt │ ├── destroy_service_account.sh.tt │ └── set_space_egress.sh.tt ├── owasp-scan ├── pa11y-scan └── with-server ├── browserslistrc ├── config └── environments │ ├── ci.rb │ └── staging.rb ├── doc ├── adr │ ├── 0001-record-architecture-decisions.md.tt │ ├── 0002-initial-architecture-decisions.md.tt │ ├── 0003-security-scans.md.tt │ └── 0004-rails-csp-compliant-script-tag-helpers.md.tt └── compliance │ ├── README.md │ ├── TODO.md │ ├── apps │ └── application.boundary.md.tt │ └── rendered │ └── apps │ └── .keep ├── editorconfig ├── env ├── githooks └── pre-commit ├── lib └── tasks │ ├── cf.rake │ └── scanning.rake ├── pa11y.js ├── pa11yci.js.tt └── zap.conf /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a new issue for a problem or bug 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Bug/Issue 11 | 12 | 13 | 14 | ## What was the problem? 15 | 16 | 17 | 18 | 24 | 25 | 33 | 34 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New User Story 3 | about: Create a new issue in using the "user story" format. 4 | title: '' 5 | labels: 'story' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Story 11 | 12 | As a ***TYPE_OF_USER***, 13 | I would like to ***NEW_FUNCTIONALITY***, 14 | So that I can ***ACCEPTED_OUTCOME*** 15 | 16 | ## Solution 17 | 18 | 19 | 20 | ```[tasklist] 21 | ### Tasks 22 | - [ ] One task 23 | - [ ] Another task 24 | ``` 25 | 26 | ## Acceptance Criteria 27 | 28 | 29 | 30 | - [ ] One criteria 31 | - [ ] Another criteria 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 🎫 Addresses issue: #0 4 | 7 | 8 | ## 🛠 Summary of changes 9 | 10 | 13 | 14 | 23 | 24 | 39 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ruby: 18 | - '3.2' 19 | - '3.3' 20 | - '3.4' 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up Ruby 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | bundler-cache: true 29 | - name: Run the default task 30 | run: bundle exec rake 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | **/.trestle 10 | 11 | # rspec failure tracking 12 | .rspec_status 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | # For available configuration options, see: 2 | # https://github.com/testdouble/standard 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at ryan.ahearn@gsa.gov. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in rails-template-18f.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.2" 9 | 10 | gem "byebug" 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | As a work of the [United States government](https://www.usa.gov/), this project is in the public domain within the United States of America. 4 | 5 | Additionally, we waive copyright and related rights in the work worldwide through the CC0 1.0 Universal public domain dedication. 6 | 7 | ## CC0 1.0 Universal Summary 8 | 9 | This is a human-readable summary of the [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode). 10 | 11 | ### No Copyright 12 | 13 | The person who associated a work with this deed has dedicated the work to the public domain by waiving all of their rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. 14 | 15 | You can copy, modify, distribute, and perform the work, even for commercial purposes, all without asking permission. 16 | 17 | ### Other Information 18 | 19 | In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights. 20 | 21 | Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. When using or citing the work, you should not imply endorsement by the author or the affirmer. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | require "standard/rake" 9 | 10 | task default: %i[spec standard] 11 | 12 | task :release do 13 | # adding a custom release task because I can't get the default `rake release` to play nicely with my 14 | # passkey login to rubygems.org on GFE, so I need to use the `gem push --otp` version. 15 | # set the environment variable gem_push=false to enable this block 16 | gemhelper = Bundler::GemHelper.instance 17 | unless gemhelper.send :gem_push? 18 | gemspec = gemhelper.gemspec 19 | Bundler.ui.warn "Next step: publish the #{gemspec.name} gem with:" 20 | Bundler.ui.warn "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem --otp OTP" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only certain branches are supported with security updates. 6 | 7 | | Version (branch) | Supported | 8 | | ---------------- | ----------- | 9 | | main | :white_check_mark: | 10 | | rails-7.0 | :white_check_mark: | 11 | | other | :x: | 12 | 13 | When using this code or reporting vulnerability please be sure to use supported branches and the most recent release tag. 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use the `Report a vulnerability` link at https://github.com/GSA-TTS/rails-template/security to report a security vulnerability 18 | on a supported branch of this repository. 19 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "rails/all" 6 | require "rails_template_18f" 7 | 8 | # You can add fixtures and/or initialization code here to make experimenting 9 | # with your gem easier. You can also use a different console, if you like. 10 | 11 | # (If you use this, don't forget to add pry to your Gemfile!) 12 | # require "pry" 13 | # Pry.start 14 | 15 | require "irb" 16 | IRB.start(__FILE__) 17 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /exe/rails_template_18f: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "thor" 5 | require_relative "../lib/rails_template18f/version" 6 | 7 | class CLI < Thor 8 | include Thor::Actions 9 | 10 | desc "new APP_DIRECTORY [options] [rails new arguments]", "Run rails new with 18F flavor" 11 | option :hotwire, type: :boolean, default: false, desc: "Enable hotwire JS framework" 12 | long_desc <<-LONGDESC 13 | Create a new rails application in as customized by 14 | 15 | * railsrc: https://github.com/18F/rails-template/blob/main/railsrc 16 | 17 | * template.rb: https://github.com/18F/rails-template/blob/main/template.rb 18 | 19 | with --hotwire option, includes the Hotwire JS framework 20 | 21 | all other arguments will be passed as-is to `rails new` 22 | LONGDESC 23 | def new(app_directory, *rails_arguments) 24 | gem_path = File.expand_path("..", __dir__) 25 | railsrc = options[:hotwire] ? "railsrc-hotwire" : "railsrc" 26 | run "rails new #{app_directory} --rc=#{File.join(gem_path, railsrc)} --template=#{File.join(gem_path, "template.rb")} #{rails_arguments.join(" ")}" 27 | end 28 | 29 | desc "version", "Output gem version" 30 | def version 31 | puts RailsTemplate18f::VERSION 32 | end 33 | 34 | def self.exit_on_failure? 35 | true 36 | end 37 | end 38 | 39 | CLI.start(ARGV) 40 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/active_storage/active_storage_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators" 4 | 5 | module RailsTemplate18f 6 | module Generators 7 | class ActiveStorageGenerator < ::Rails::Generators::Base 8 | include Base 9 | 10 | desc <<~DESC 11 | Description: 12 | Document use of Clamav as ActiveStorage scanner 13 | DESC 14 | 15 | def configure_active_storage 16 | generate "rails_template18f:cloud_gov_config", inline: true 17 | rails_command "active_storage:install", inline: true 18 | comment_lines "config/environments/production.rb", /active_storage\.service/ 19 | insert_into_file "config/environments/production.rb", "\n config.active_storage.service = :amazon", after: /active_storage\.service.*$/ 20 | environment "config.active_storage.service = :local", env: "ci" 21 | append_to_file "config/storage.yml", <<~EOYAML 22 | 23 | <% cgc = CloudGovConfig.new %> 24 | amazon: 25 | service: S3 26 | access_key_id: <%= cgc.dig(:s3, :credentials, :access_key_id) %> 27 | secret_access_key: <%= cgc.dig(:s3, :credentials, :secret_access_key) %> 28 | region: us-gov-west-1 29 | bucket: <%= cgc.dig(:s3, :credentials, :bucket) %> 30 | EOYAML 31 | end 32 | 33 | def install_gems 34 | faraday_installed = gem_installed?("faraday") 35 | middleware_installed = gem_installed?("faraday-multipart") 36 | sdk_installed = gem_installed?("aws-sdk-s3") 37 | return if faraday_installed && middleware_installed && sdk_installed 38 | gem "faraday", "~> 2.12" unless faraday_installed 39 | gem "faraday-multipart", "~> 1.1" unless middleware_installed 40 | unless sdk_installed 41 | gem_group :production do 42 | gem "aws-sdk-s3", "~> 1.176" 43 | end 44 | end 45 | bundle_install 46 | end 47 | 48 | def create_scanned_upload_model_and_job 49 | generate :migration, "CreateFileUploads", "file:attachment", "record:references{polymorphic}", "scan_status:string", inline: true 50 | migration_file = Dir.glob(File.expand_path(File.join("db", "migrate", "[0-9]*_*.rb"), destination_root)).grep(/\d+_create_file_uploads.rb$/).first 51 | unless migration_file.nil? 52 | gsub_file migration_file, ":scan_status", ":scan_status, null: false, default: \"uploaded\"" 53 | end 54 | directory "app" 55 | directory "spec" 56 | end 57 | 58 | def configure_local_clamav_runner 59 | append_to_file "Procfile.dev", "clamav: docker run --rm -p 9443:9443 ghcr.io/gsa-tts/clamav-rest/clamav:latest\n" 60 | end 61 | 62 | def configure_clamav_env_var 63 | append_to_file ".env", <<~EOM 64 | 65 | # CLAMAV_API_URL tells FileScanJob where to send files for virus scans 66 | CLAMAV_API_URL=https://localhost:9443 67 | EOM 68 | insert_into_file file_path("terraform/app.tf"), < ex 21 | file_upload&.update_columns scan_status: "scan_failed", updated_at: Time.now 22 | raise ex 23 | end 24 | 25 | def connection 26 | @connection ||= Faraday.new( 27 | url: ENV["CLAMAV_API_URL"], 28 | ssl: {verify: false} 29 | ) do |f| 30 | f.request :multipart 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/active_storage/templates/app/models/file_upload.rb: -------------------------------------------------------------------------------- 1 | class FileUpload < ApplicationRecord 2 | belongs_to :record, polymorphic: true 3 | has_one_attached :file 4 | 5 | delegate :open, :content_type, to: :file 6 | 7 | validates_presence_of :file 8 | validates_inclusion_of :scan_status, in: %w[uploaded scan_failed scanned quarantined] 9 | 10 | after_commit :scan 11 | 12 | def clean? 13 | scan_status == "scanned" 14 | end 15 | 16 | def filename 17 | file.filename.to_s 18 | end 19 | 20 | private 21 | 22 | def scan 23 | FileScanJob.perform_later self 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/active_storage/templates/doc/adr/clamav.md.tt: -------------------------------------------------------------------------------- 1 | # <%= @next_adr_id %>. ClamAV File Scanning 2 | 3 | Date: <%= Date.today.iso8601 %> 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | In order to satisfy the [RA-5](https://nvd.nist.gov/800-53/Rev4/control/RA-5) 12 | control around vulnerability scanning, we wish to scan all user-uploaded files 13 | with a malware detection service. These files are user-supplied, and thus 14 | cannot fit into our other security controls built into the CI/CD pipeline. 15 | 16 | ## Decision 17 | 18 | We will run a ClamAV daemon, fronted by a REST API that we will pass all user-uploaded 19 | files through as part of the upload process. 20 | <% if terraform_dir_exists? %> 21 | The ClamAV app is deployed along with other infrastructure by terraform. 22 | <% else %> 23 | The ClamAV app is based on a [separate government-controlled repository](https://github.com/18f/clamav-api-cg-app) 24 | <% end %> 25 | 26 | ## Consequences 27 | 28 | While our user-supplied files will now have vulnerability protection, this architecture 29 | does not provide scanning of the application itself. Therefore we must find other solutions 30 | to addressing SI-3 or RA-5 in the context of the application. 31 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/active_storage/templates/oscal/component-definitions/active_storage/component-definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "component-definition": { 3 | "uuid": "6c8efe45-ab46-4d02-846e-5d58b4797a3e", 4 | "metadata": { 5 | "title": "ActiveStorage Component Definition.", 6 | "last-modified": "2024-06-10T17:31:06.312964+00:00", 7 | "version": "0.0.1", 8 | "oscal-version": "1.1.2" 9 | }, 10 | "components": [ 11 | { 12 | "uuid": "a206dda7-d1f6-451c-8a0f-b6f4e8bf22d0", 13 | "type": "software", 14 | "title": "ClamAV", 15 | "description": "ClamAV malware scanner", 16 | "props": [ 17 | { 18 | "name": "Rule_Id", 19 | "value": "properly-configured", 20 | "remarks": "rule_config" 21 | }, 22 | { 23 | "name": "Rule_Description", 24 | "value": "System owner has configured the system to properly run the ClamAV scanner and send files to it on upload", 25 | "remarks": "rule_config" 26 | } 27 | ], 28 | "control-implementations": [ 29 | { 30 | "uuid": "e1a02625-cb99-48e6-8240-90f2fdcc8481", 31 | "source": "trestle://profiles/gsa-moderate/profile.json", 32 | "description": "Controls satisfied via use of the ClamAV malware scanning app", 33 | "implemented-requirements": [ 34 | { 35 | "uuid": "4c53c056-dbbd-4889-b268-e1d50bc1fd88", 36 | "control-id": "si-3", 37 | "description": "", 38 | "statements": [ 39 | { 40 | "statement-id": "si-3_smt.a", 41 | "uuid": "9621f3b7-878f-487a-bfa1-bbd9d2111e25", 42 | "description": "The system employs ClamAV to detect and quarantine malicious code in user-uploaded files.", 43 | "props": [ 44 | { 45 | "name": "Rule_Id", 46 | "value": "properly-configured" 47 | } 48 | ] 49 | }, 50 | { 51 | "statement-id": "si-3_smt.b", 52 | "uuid": "850c1163-5c94-4018-9593-0f8e908ace2f", 53 | "description": "ClamAV is configured to automatically update malicious code detection signatures on a daily basis.", 54 | "props": [ 55 | { 56 | "name": "Rule_Id", 57 | "value": "properly-configured" 58 | } 59 | ] 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | ] 66 | } 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/active_storage/templates/spec/jobs/file_scan_job_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe FileScanJob, type: :job do 4 | subject { described_class.new } 5 | let(:scanned_file) { double(clean?: true) } 6 | let(:unscanned_file) { double(id: 1, clean?: false, content_type: "text/plain", filename: "test.txt") } 7 | let(:success_response) { double(success?: true) } 8 | let(:error_response) { double(success?: false, body: "Error response body") } 9 | 10 | it "deals with a nil argument" do 11 | expect { subject.perform nil }.to_not raise_error 12 | end 13 | 14 | it "returns quickly if the file is already scanned" do 15 | expect { subject.perform scanned_file }.to_not raise_error 16 | end 17 | 18 | it "updates the scan_status after scanning the file" do 19 | now = Time.now 20 | allow(Time).to receive(:now).and_return now 21 | allow(unscanned_file).to receive(:open).and_yield __FILE__ 22 | expect(unscanned_file).to receive(:update_columns).with scan_status: "scanned", updated_at: Time.now 23 | allow(subject).to receive(:connection).and_return double(post: success_response) 24 | subject.perform unscanned_file 25 | end 26 | 27 | it "marks the file as quarantined when dirty" do 28 | now = Time.now 29 | allow(Time).to receive(:now).and_return now 30 | allow(unscanned_file).to receive(:open).and_yield __FILE__ 31 | expect(unscanned_file).to receive(:update_columns).with scan_status: "quarantined", updated_at: Time.now 32 | allow(subject).to receive(:connection).and_return double(post: error_response) 33 | subject.perform unscanned_file 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/active_storage/templates/spec/models/file_upload_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe FileUpload, type: :model do 4 | subject { described_class.new } 5 | 6 | describe "validations" do 7 | before do 8 | subject.file.attach(io: File.open(__FILE__), filename: "file_upload_spec.rb") 9 | end 10 | 11 | %w[uploaded scan_failed scanned quarantined].each do |valid_status| 12 | it "allows scan_status=#{valid_status}" do 13 | pending "#{described_class.name} cannot be valid without a record to belong_to" 14 | subject.scan_status = valid_status 15 | expect(subject).to be_valid 16 | end 17 | end 18 | 19 | it "is invalid with a bad scan_status" do 20 | subject.scan_status = "invalid" 21 | expect(subject).to_not be_valid 22 | end 23 | end 24 | 25 | describe "#clean?" do 26 | it "returns true when scan_status is scanned" do 27 | subject.scan_status = "scanned" 28 | expect(subject).to be_clean 29 | end 30 | 31 | it "returns false when scan_status is not scanned" do 32 | subject.scan_status = "uploaded" 33 | expect(subject).to_not be_clean 34 | subject.scan_status = "quarantined" 35 | expect(subject).to_not be_clean 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/auditree/auditree_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators" 4 | 5 | module RailsTemplate18f 6 | module Generators 7 | class AuditreeGenerator < ::Rails::Generators::Base 8 | include Base 9 | 10 | class_option :tag, desc: "Which auditree docker tag to use. Defaults to `latest`" 11 | class_option :git_email, desc: "Email address to associate with commits to the evidence locker" 12 | class_option :evidence_locker, desc: "Git repository address to store evidence in. Defaults to a TKTK address." 13 | 14 | desc <<~DESC 15 | Description: 16 | Set up auditree validation checking with https://github.com/GSA-TTS/devtools-auditree. 17 | 18 | This generator is still experimental. 19 | DESC 20 | 21 | def copy_bin 22 | template "bin/auditree" 23 | chmod "bin/auditree", 0o755 24 | end 25 | 26 | def copy_github_actions 27 | if file_exists? ".github/workflows" 28 | directory "github", ".github" 29 | 30 | # insert plant-helper calls in rspec 31 | insert_into_file ".github/workflows/rspec.yml", < "$config" 83 | exit 0 84 | fi 85 | 86 | volume_args+=("-v" "$cwd/$config":/app/auditree.template.json:ro) 87 | volume_args+=("-v" "$cwd/$cdef":/app/cdef.json:ro) 88 | if [ "$ar_output" != "" ]; then 89 | mkdir -p "$ar_output" 90 | chmod a+w "$ar_output" 91 | volume_args+=("-v" "$ar_output":/tmp/auditree:rw) 92 | if [ "$command" = "check" ]; then 93 | command="check /tmp/auditree" 94 | fi 95 | fi 96 | 97 | if [ "$command" = "bash" ]; then 98 | docker run -e GITHUB_TOKEN -e CF_USERNAME -e CF_PASSWORD -e GIT_EMAIL="$email" \ 99 | "${volume_args[@]}" -it --rm $image:$tag $command "$@" 100 | else 101 | docker run -e GITHUB_TOKEN -e CF_USERNAME -e CF_PASSWORD -e GIT_EMAIL="$email" \ 102 | "${volume_args[@]}" --rm $image:$tag $command "$@" 103 | fi 104 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/auditree/templates/github/actions/auditree-cmd/action.yml.tt: -------------------------------------------------------------------------------- 1 | name: "Run an auditree-devtools command" 2 | description: "Sets up workspace for running a single command in auditree-devtools" 3 | inputs: 4 | tag: 5 | description: auditree-devtools tag to use. 6 | required: false 7 | default: <%= docker_auditree_tag %> 8 | cmd: 9 | description: Command to run within auditree-devtools 10 | required: true 11 | email: 12 | description: Git user email to attribute to evidence updates 13 | required: false 14 | default: "<%= git_email %>" 15 | config_template: 16 | description: Auditree config file template 17 | required: false 18 | default: config/auditree.template.json 19 | cdef: 20 | description: OSCAL Component Definition being used as baseline for assessment results 21 | required: false 22 | default: doc/compliance/oscal/component-definitions/devtools_cloud_gov/component-definition.json 23 | volume: 24 | description: Freeform volume string to mount another file in the auditree image 25 | required: false 26 | default: "" 27 | runs: 28 | using: "composite" 29 | steps: 30 | - name: Run cmd 31 | shell: bash 32 | if: ${{ inputs.volume == '' }} 33 | run: 34 | bin/auditree -t ${{ inputs.tag }} -a ${{ inputs.config_template }} -c ${{ inputs.cdef }} 35 | -e "${{ inputs.email }}" ${{ inputs.cmd }} 36 | 37 | - name: Run cmd with volume 38 | shell: bash 39 | if: ${{ inputs.volume != '' }} 40 | run: 41 | bin/auditree -t ${{ inputs.tag }} -a ${{ inputs.config_template }} -c ${{ inputs.cdef }} 42 | -e "${{ inputs.email }}" -v ${{ inputs.volume }} ${{ inputs.cmd }} 43 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/auditree/templates/github/workflows/auditree-validation.yml.tt: -------------------------------------------------------------------------------- 1 | name: Run Auditree Checks 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # cron format: 'minute hour dayofmonth month dayofweek' 7 | # this will run at 11am UTC every day (6am EST / 7am EDT) 8 | - cron: '0 11 * * *' 9 | 10 | jobs: 11 | run_auditree: 12 | name: Fetch and check auditree evidence 13 | runs-on: ubuntu-latest 14 | environment: production 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Fetch evidence 19 | uses: ./.github/actions/auditree-cmd 20 | env: 21 | CF_USERNAME: ${{ secrets.CF_USERNAME }} 22 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 23 | GITHUB_TOKEN: ${{ secrets.AUDITREE_GITHUB_TOKEN }} 24 | with: 25 | cmd: fetch 26 | 27 | - name: Check evidence 28 | uses: ./.github/actions/auditree-cmd 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.AUDITREE_GITHUB_TOKEN }} 31 | with: 32 | cmd: -o check 33 | 34 | - name: Save results 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: auditree_assessment_results 38 | path: tmp/auditree/auditree.json 39 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/circleci/circleci_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators" 4 | 5 | module RailsTemplate18f 6 | module Generators 7 | class CircleciGenerator < ::Rails::Generators::Base 8 | include Base 9 | include CloudGovOptions 10 | 11 | desc <<~DESC 12 | Description: 13 | Install CircleCI pipeline files 14 | DESC 15 | 16 | def install_needed_gems 17 | gem_name = "rspec_junit_formatter" 18 | return if gem_installed? gem_name 19 | gem gem_name, "~> 0.6", group: :test 20 | bundle_install 21 | end 22 | 23 | def install_pipeline 24 | directory "circleci", ".circleci" 25 | copy_file "docker-compose.ci.yml" 26 | template "Dockerfile.ci" 27 | copy_file "bin/ci-server-start", mode: :preserve 28 | end 29 | 30 | def update_readme 31 | if file_content("README.md").match?(/^## CI\/CD$/) 32 | insert_into_file "README.md", readme_cicd, after: "## CI/CD\n" 33 | insert_into_file "README.md", readme_staging_deploy, after: "#### Staging\n" 34 | insert_into_file "README.md", readme_prod_deploy, after: "#### Production\n" 35 | insert_into_file "README.md", readme_credentials, after: "#### Credentials and other Secrets\n" 36 | else 37 | append_to_file "README.md", <<~EOM 38 | ## CI/CD 39 | #{readme_cicd} 40 | 41 | ### Deployment 42 | 43 | #### Staging 44 | #{readme_staging_deploy} 45 | 46 | #### Production 47 | #{readme_prod_deploy} 48 | 49 | #### Credentials and other Secrets 50 | #{readme_credentials} 51 | EOM 52 | end 53 | end 54 | 55 | def update_boundary_diagram 56 | boundary_filename = "doc/compliance/apps/application.boundary.md" 57 | insert_into_file boundary_filename, <-node 2 | 3 | ENV PORT=3000 4 | EXPOSE $PORT 5 | 6 | COPY --chown=circleci . /home/circleci/project 7 | RUN bundle install --deployment 8 | RUN yarn install --frozen-lockfile 9 | 10 | ENV RAILS_ENV=ci 11 | 12 | CMD ["./bin/ci-server-start"] 13 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/circleci/templates/bin/ci-server-start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # this script is used by docker-compose and Dockerfile.ci to start up a server 4 | # for running OWASP in CircleCI 5 | 6 | dockerize -wait tcp://db:5432 -timeout 1m 7 | bundle exec rails db:schema:load --trace 8 | bundle exec rails server -b 0.0.0.0 9 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/circleci/templates/docker-compose.ci.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | services: 3 | web: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile.ci 7 | user: ${CURRENT_USER:-root} 8 | networks: 9 | - ci_network 10 | ports: 11 | - "3000:3000" 12 | depends_on: 13 | - db 14 | environment: 15 | RAILS_ENV: ci 16 | DATABASE_URL: postgres://circleci:notasecret@db:5432/ci_db 17 | RAILS_MASTER_KEY: $RAILS_MASTER_KEY 18 | db: 19 | image: cimg/postgres:12.9 20 | environment: 21 | POSTGRES_USER: circleci 22 | POSTGRES_DB: ci_db 23 | POSTGRES_PASSWORD: notasecret 24 | networks: 25 | - ci_network 26 | networks: 27 | ci_network: 28 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/cloud_gov_config/cloud_gov_config_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators" 4 | 5 | module RailsTemplate18f 6 | module Generators 7 | class CloudGovConfigGenerator < ::Rails::Generators::Base 8 | include Base 9 | 10 | desc <<~DESC 11 | Description: 12 | Install a helper class to retrieve configuration from ENV["VCAP_SERVICES"] 13 | DESC 14 | 15 | def install_model_and_test 16 | copy_file "app/models/cloud_gov_config.rb" 17 | copy_file "spec/models/cloud_gov_config_spec.rb" 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/cloud_gov_config/templates/app/models/cloud_gov_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CloudGovConfig 4 | attr_reader :vcap_services 5 | 6 | def initialize(env = ENV["VCAP_SERVICES"]) 7 | @vcap_services = env.blank? ? {} : JSON.parse(env).with_indifferent_access 8 | end 9 | 10 | def dig(*path) 11 | first, *rest = path 12 | vcap_services[first]&.first&.dig(*rest) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/cloud_gov_config/templates/spec/models/cloud_gov_config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe CloudGovConfig, type: :model do 6 | describe "#dig" do 7 | [nil, "", "{}"].each do |blank| 8 | context "VCAP_SERVICES is #{blank.inspect}" do 9 | subject { described_class.new blank } 10 | it "returns nil" do 11 | expect(subject.dig(:s3, :credentials, :bucket)).to be_nil 12 | end 13 | end 14 | end 15 | 16 | context "VCAP_SERVICES is set" do 17 | subject { described_class.new vcap } 18 | let(:bucket_name) { "bucket-name" } 19 | let(:vcap) { 20 | { 21 | s3: [{ 22 | credentials: { 23 | bucket: bucket_name 24 | } 25 | }] 26 | }.to_json 27 | } 28 | 29 | it "can find a path" do 30 | expect(subject.dig(:s3, :credentials, :bucket)).to eq bucket_name 31 | end 32 | 33 | it "returns nil for a missing path" do 34 | expect(subject.dig(:s3, :missing)).to be_nil 35 | end 36 | 37 | it "returns nil for a missing service" do 38 | expect(subject.dig(:rds, :credentials)).to be_nil 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/dap/dap_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators" 4 | 5 | module RailsTemplate18f 6 | module Generators 7 | class DapGenerator < ::Rails::Generators::Base 8 | include Base 9 | 10 | class_option :agency_code, default: "GSA", desc: "Agency code to track DAP metrics" 11 | 12 | desc <<~DESC 13 | Description: 14 | Install JS snippet for Digital Analytics Program (DAP) 15 | DESC 16 | 17 | def update_content_security_policy 18 | csp_file = "config/initializers/content_security_policy.rb" 19 | gsub_file csp_file, /(policy.img_src .*)$/, '\1, "https://www.google-analytics.com"' 20 | gsub_file csp_file, /(policy.script_src .*)$/, '\1, "https://dap.digitalgov.gov", "https://www.google-analytics.com"' 21 | if file_content(csp_file).match?(/policy.connect_src/) 22 | gsub_file csp_file, /(policy.connect_src .*)$/, '\1, "https://dap.digitalgov.gov", "https://www.google-analytics.com"' 23 | else 24 | gsub_file csp_file, /((#?)(\s+)policy.script_src .*)$/, "\\1\n\\2\\3policy.connect_src :self, \"https://dap.digitalgov.gov\", \"https://www.google-analytics.com\"" 25 | end 26 | end 27 | 28 | def install_js_snippet 29 | insert_into_file "app/views/layouts/application.html.erb", </ 30 | 31 | <% if Rails.env.production? %> 32 | 33 | <%= javascript_include_tag "https://dap.digitalgov.gov/Universal-Federated-Analytics-Min.js?agency=#{options[:agency_code]}", async: true, id:"_fed_an_ua_tag" %> 34 | <% end %> 35 | EODAP 36 | end 37 | 38 | def update_readme 39 | insertion_regex = /^## Documentation$/ 40 | if file_content("README.md").match?(insertion_regex) 41 | insert_into_file "README.md", readme, before: insertion_regex 42 | else 43 | append_to_file "README.md", readme 44 | end 45 | end 46 | 47 | def update_boundary_diagram 48 | boundary_filename = "doc/compliance/apps/application.boundary.md" 49 | insert_into_file boundary_filename, <' 17 | cache: 'yarn' 18 | - name: Install yarn dependencies 19 | shell: bash 20 | run: yarn install --frozen-lockfile 21 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/actions/setup-project/action.yml.tt: -------------------------------------------------------------------------------- 1 | name: Set up project with database 2 | description: Setup Ruby, Javascript, and load the database schema into a running postgres db 3 | inputs: 4 | rails_env: 5 | description: RAILS_ENV to set. Defaults to ci 6 | required: false 7 | default: ci 8 | database_url: 9 | description: DATABASE_URL to set 10 | required: false 11 | default: postgres://cidbuser:postgres@localhost:5432/<%= app_name %>_test 12 | outputs: 13 | database_url: 14 | value: ${{ inputs.database_url }} 15 | runs: 16 | using: composite 17 | steps: 18 | - name: Precompile assets 19 | uses: ./.github/actions/compile-assets 20 | with: 21 | rails_env: ${{ inputs.rails_env }} 22 | save_cache: true 23 | 24 | - name: Set up database 25 | env: 26 | RAILS_ENV: ${{ inputs.rails_env }} 27 | SECRET_KEY_BASE: not-actually-secret 28 | DATABASE_URL: ${{ inputs.database_url }} 29 | shell: bash 30 | run: bundle exec rake db:schema:load 31 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/dependabot.yml.tt: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: npm 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | - package-ecosystem: github-actions 14 | directory: "/" 15 | schedule: 16 | interval: daily 17 | open-pull-requests-limit: 10 18 | - package-ecosystem: terraform 19 | directories: 20 | - "/terraform" 21 | schedule: 22 | interval: weekly 23 | open-pull-requests-limit: 10 24 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/assemble-ssp.yml.tt: -------------------------------------------------------------------------------- 1 | name: Assemble SSPP updates 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | 8 | jobs: 9 | assemble_ssp: 10 | name: Assemble SSPP updates and save artifact 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Assemble final SSPP 16 | uses: ./.github/actions/trestle-cmd 17 | with: 18 | cmd: trestle assemble -n <%= app_name %> system-security-plan 19 | 20 | - name: Render final SSPP 21 | uses: ./.github/actions/trestle-cmd 22 | with: 23 | cmd: render-ssp 24 | 25 | - name: Transform SSPP to PDF 26 | run: docker run --rm -u "$(id -u):$(id -g)" -v "$GITHUB_WORKSPACE/doc/compliance/oscal/ssp-render:/data" pandoc/latex <%= app_name %>_ssp.md -o <%= app_name %>_ssp.pdf 27 | 28 | - name: Save artifact 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: <%= app_name %>_SSPP 32 | path: | 33 | doc/compliance/oscal/dist/system-security-plans/<%= app_name %>.json 34 | doc/compliance/oscal/ssp-render/<%= app_name %>_ssp.md 35 | doc/compliance/oscal/ssp-render/<%= app_name %>_ssp.pdf 36 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/brakeman-analysis.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow integrates Brakeman with GitHub's Code Scanning feature 7 | # Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications 8 | 9 | name: Brakeman Scan 10 | 11 | on: 12 | push: 13 | branches: [ main ] 14 | paths-ignore: 15 | - 'doc/**' 16 | - 'README.md' 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | # cron format: 'minute hour dayofmonth month dayofweek' 22 | # this will run at noon UTC each Monday (7am EST / 8am EDT) 23 | - cron: '0 12 * * 1' 24 | 25 | permissions: 26 | contents: read 27 | security-events: write 28 | 29 | jobs: 30 | brakeman-scan: 31 | name: Brakeman Scan 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - uses: ./.github/actions/setup-languages 37 | 38 | # Execute Brakeman CLI and generate a SARIF output with the security issues identified during the analysis 39 | - name: Scan 40 | continue-on-error: true 41 | run: | 42 | bin/brakeman --no-pager --ensure-ignore-notes -f sarif -o output.sarif.json 43 | 44 | # Upload the SARIF file generated in the previous step 45 | - name: Upload SARIF 46 | uses: github/codeql-action/upload-sarif@v3 47 | with: 48 | sarif_file: output.sarif.json 49 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/dependency-scans.yml: -------------------------------------------------------------------------------- 1 | name: Ruby and Javascript dependency scans 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - 'doc/**' 8 | - 'README.md' 9 | pull_request: 10 | branches: [ main ] 11 | schedule: 12 | # cron format: 'minute hour dayofmonth month dayofweek' 13 | # this will run at noon UTC every day (7am EST / 8am EDT) 14 | - cron: '0 12 * * *' 15 | 16 | jobs: 17 | bundle-audit: 18 | name: Bundle audit 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - uses: ./.github/actions/setup-languages 25 | 26 | - name: Update advisory database and run checks 27 | run: bundle exec rake bundler:audit 28 | 29 | yarn-audit: 30 | name: Yarn audit 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - uses: ./.github/actions/setup-languages 37 | 38 | - name: Run yarn audit 39 | run: bundle exec rake yarn:audit 40 | 41 | ruby-bom: 42 | name: Ruby SBOM Generation 43 | runs-on: ubuntu-latest 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: ./.github/actions/setup-languages 48 | - name: Install cyclonedx 49 | run: gem install cyclonedx-ruby 50 | - name: Generate BOM 51 | run: cyclonedx-ruby -p . -o ruby_bom.xml 52 | - name: Save BOM 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: ruby-bom 56 | path: ./ruby_bom.xml 57 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/deploy-production.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Production 2 | 3 | on: 4 | push: 5 | branches: [ production ] 6 | paths-ignore: 7 | - 'doc/**' 8 | - 'README.md' 9 | 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | 14 | jobs: 15 | build-assets: 16 | name: Compile and clean assets 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Compile assets 21 | uses: ./.github/actions/compile-assets 22 | with: 23 | rails_env: production 24 | save_cache: true 25 | - name: Upload assets 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: production-assets 29 | path: public/assets 30 | 31 | deploy: 32 | name: Deploy to production 33 | runs-on: ubuntu-latest 34 | needs: build-assets 35 | environment: production 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - name: Download assets 42 | uses: actions/download-artifact@v4 43 | with: 44 | name: production-assets 45 | path: public/assets 46 | 47 | - name: Terraform apply 48 | uses: dflook/terraform-apply@v1 49 | env: 50 | CF_API_URL: "https://api.fr.cloud.gov" 51 | CF_USER: ${{ secrets.CF_USERNAME }} 52 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 53 | TF_VAR_cf_user: ${{ secrets.CF_USERNAME }} 54 | TF_VAR_rails_master_key: ${{ secrets.RAILS_MASTER_KEY }} 55 | TERRAFORM_PRE_RUN: | 56 | apt-get update 57 | apt-get install -y zip 58 | with: 59 | path: terraform 60 | var_file: terraform/production.tfvars 61 | backend_config: > 62 | access_key=${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} 63 | secret_key=${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} 64 | bucket=${{ secrets.TERRAFORM_STATE_BUCKET_NAME }} 65 | key=terraform.tfstate.production 66 | 67 | - name: Save app zip for debugging 68 | if: failure() 69 | uses: actions/upload-artifact@v4 70 | with: 71 | name: app-src-apply 72 | path: terraform/dist/src.zip 73 | compression-level: 0 74 | retention-days: 1 75 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/deploy-staging.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Staging 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - 'doc/**' 8 | - 'README.md' 9 | 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | 14 | jobs: 15 | build-assets: 16 | name: Compile and clean assets 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Compile assets 21 | uses: ./.github/actions/compile-assets 22 | with: 23 | rails_env: staging 24 | save_cache: true 25 | - name: Upload assets 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: staging-assets 29 | path: public/assets 30 | 31 | deploy: 32 | name: Deploy to staging 33 | runs-on: ubuntu-latest 34 | needs: build-assets 35 | environment: staging 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - name: Download assets 42 | uses: actions/download-artifact@v4 43 | with: 44 | name: staging-assets 45 | path: public/assets 46 | 47 | - name: Terraform apply 48 | uses: dflook/terraform-apply@v1 49 | env: 50 | CF_API_URL: "https://api.fr.cloud.gov" 51 | CF_USER: ${{ secrets.CF_USERNAME }} 52 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 53 | TF_VAR_cf_user: ${{ secrets.CF_USERNAME }} 54 | TF_VAR_rails_master_key: ${{ secrets.RAILS_MASTER_KEY }} 55 | TERRAFORM_PRE_RUN: | 56 | apt-get update 57 | apt-get install -y zip 58 | with: 59 | path: terraform 60 | var_file: terraform/staging.tfvars 61 | backend_config: > 62 | access_key=${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} 63 | secret_key=${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} 64 | bucket=${{ secrets.TERRAFORM_STATE_BUCKET_NAME }} 65 | key=terraform.tfstate.staging 66 | 67 | - name: Save app zip for debugging 68 | if: failure() 69 | uses: actions/upload-artifact@v4 70 | with: 71 | name: app-src-apply 72 | path: terraform/dist/src.zip 73 | compression-level: 0 74 | retention-days: 1 75 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/owasp-daily-scan.yml.tt: -------------------------------------------------------------------------------- 1 | name: OWASP ZAP daily scan 2 | 3 | on: 4 | schedule: 5 | # cron format: 'minute hour dayofmonth month dayofweek' 6 | # this will run at noon UTC every day (7am EST / 8am EDT) 7 | - cron: '0 12 * * *' 8 | 9 | permissions: 10 | contents: read 11 | issues: write 12 | 13 | jobs: 14 | owasp-scan: 15 | name: OWASP ZAP Scan 16 | runs-on: ubuntu-latest 17 | services: 18 | postgres: 19 | image: postgres 20 | options: >- 21 | --health-cmd pg_isready 22 | --health-interval 10s 23 | --health-timeout 5s 24 | --health-retries 5 25 | ports: ["5432:5432"] 26 | env: 27 | POSTGRES_DB: <%= app_name %>_test 28 | POSTGRES_USER: cidbuser 29 | POSTGRES_PASSWORD: postgres 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Touch staging cache 35 | uses: ./.github/actions/compile-assets 36 | with: 37 | rails_env: staging 38 | - name: Touch production cache 39 | uses: ./.github/actions/compile-assets 40 | with: 41 | rails_env: production 42 | 43 | - id: setup 44 | uses: ./.github/actions/setup-project 45 | 46 | - uses: ./.github/actions/run-server 47 | with: 48 | database_url: ${{ steps.setup.outputs.database_url }} 49 | 50 | - name: Run OWASP Full Scan 51 | uses: zaproxy/action-full-scan@v0.12.0 52 | with: 53 | token: ${{ secrets.GITHUB_TOKEN }} 54 | docker_name: 'ghcr.io/zaproxy/zaproxy:weekly' 55 | target: 'http://localhost:3000/' 56 | fail_action: true 57 | rules_file_name: 'zap.conf' 58 | cmd_options: '-I' 59 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/owasp-scan.yml.tt: -------------------------------------------------------------------------------- 1 | name: OWASP ZAP scan 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - 'doc/**' 8 | - 'README.md' 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | owasp-scan: 14 | name: OWASP ZAP Scan 15 | runs-on: ubuntu-latest 16 | services: 17 | postgres: 18 | image: postgres 19 | options: >- 20 | --health-cmd pg_isready 21 | --health-interval 10s 22 | --health-timeout 5s 23 | --health-retries 5 24 | ports: ["5432:5432"] 25 | env: 26 | POSTGRES_DB: <%= app_name %>_test 27 | POSTGRES_USER: cidbuser 28 | POSTGRES_PASSWORD: postgres 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - id: setup 34 | uses: ./.github/actions/setup-project 35 | 36 | - uses: ./.github/actions/run-server 37 | with: 38 | database_url: ${{ steps.setup.outputs.database_url }} 39 | 40 | - name: Run OWASP Baseline Scan 41 | uses: zaproxy/action-baseline@v0.14.0 42 | with: 43 | docker_name: 'ghcr.io/zaproxy/zaproxy:weekly' 44 | target: 'http://localhost:3000/' 45 | fail_action: true 46 | allow_issue_writing: false 47 | rules_file_name: 'zap.conf' 48 | cmd_options: '-I' 49 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/pa11y.yml.tt: -------------------------------------------------------------------------------- 1 | name: pa11y tests 2 | 3 | on: [pull_request] 4 | 5 | permissions: 6 | contents: read 7 | pull-requests: write 8 | 9 | jobs: 10 | pa11y_scan: 11 | name: Pa11y Scan 12 | runs-on: ubuntu-latest 13 | services: 14 | postgres: 15 | image: postgres 16 | options: >- 17 | --health-cmd pg_isready 18 | --health-interval 10s 19 | --health-timeout 5s 20 | --health-retries 5 21 | ports: ["5432:5432"] 22 | env: 23 | POSTGRES_DB: <%= app_name %>_test 24 | POSTGRES_USER: cidbuser 25 | POSTGRES_PASSWORD: postgres 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - id: setup 31 | uses: ./.github/actions/setup-project 32 | 33 | - uses: ./.github/actions/run-server 34 | with: 35 | database_url: ${{ steps.setup.outputs.database_url }} 36 | 37 | - name: Run pa11y-ci 38 | shell: bash 39 | run: | 40 | set -o pipefail 41 | yarn run pa11y-ci -c pa11yci.js 2>&1 | tee pa11y_output.txt 42 | 43 | - name: Read pa11y_output file. 44 | if: failure() 45 | id: pa11y_output 46 | uses: juliangruber/read-file-action@v1 47 | with: 48 | path: ./pa11y_output.txt 49 | 50 | - name: Comment on pull request 51 | if: failure() 52 | uses: actions/github-script@v7 53 | with: 54 | script: | 55 | const output = `Pa11y Failures detected 56 | 57 |
Show failure message 58 | 59 | \`\`\`\n 60 | ${{ steps.pa11y_output.outputs.content }} 61 | \`\`\` 62 |
`; 63 | 64 | github.rest.issues.createComment({ 65 | issue_number: context.issue.number, 66 | owner: context.repo.owner, 67 | repo: context.repo.repo, 68 | body: output 69 | }); 70 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/rspec.yml.tt: -------------------------------------------------------------------------------- 1 | name: rspec tests 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | rspec: 7 | name: Rspec 8 | runs-on: ubuntu-latest 9 | services: 10 | postgres: 11 | image: postgres 12 | options: >- 13 | --health-cmd pg_isready 14 | --health-interval 10s 15 | --health-timeout 5s 16 | --health-retries 5 17 | ports: ["5432:5432"] 18 | env: 19 | POSTGRES_DB: <%= app_name %>_test 20 | POSTGRES_USER: cidbuser 21 | POSTGRES_PASSWORD: postgres 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - id: setup 27 | uses: ./.github/actions/setup-project 28 | with: 29 | rails_env: test 30 | 31 | - name: Run rspec 32 | env: 33 | DATABASE_URL: ${{ steps.setup.outputs.database_url }}<% if oscal_dir_exists? %> 34 | rspec_oscal_output: tmp<% end %> 35 | run: bundle exec rspec 36 | <% if oscal_dir_exists? %> 37 | - name: Save assessment results 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: <%= app_name %>_assessment 41 | path: tmp/oscal 42 | <% end %> 43 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/terraform-production.yml: -------------------------------------------------------------------------------- 1 | name: Run Terraform plan in production 2 | 3 | on: 4 | pull_request: 5 | branches: [ production ] 6 | 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | 11 | jobs: 12 | build-assets: 13 | name: Compile and clean assets 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Compile assets 18 | uses: ./.github/actions/compile-assets 19 | with: 20 | rails_env: production 21 | # you may want to enable the next line to surface issues with missing assets, 22 | # but not until after you've deployed once and the cache has been created 23 | # fail_on_missing_cache: true 24 | - name: Upload assets 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: production-assets 28 | path: public/assets 29 | 30 | terraform: 31 | name: Terraform plan 32 | runs-on: ubuntu-latest 33 | needs: build-assets 34 | environment: production 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | - name: terraform validate 42 | uses: dflook/terraform-validate@v1 43 | with: 44 | path: terraform 45 | 46 | - name: terraform fmt 47 | uses: dflook/terraform-fmt-check@v1 48 | with: 49 | path: terraform 50 | 51 | - name: Download assets 52 | uses: actions/download-artifact@v4 53 | with: 54 | name: production-assets 55 | path: public/assets 56 | 57 | - name: terraform plan 58 | uses: dflook/terraform-plan@v1 59 | env: 60 | CF_API_URL: "https://api.fr.cloud.gov" 61 | CF_USER: ${{ secrets.CF_USERNAME }} 62 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 63 | TF_VAR_cf_user: ${{ secrets.CF_USERNAME }} 64 | TF_VAR_rails_master_key: ${{ secrets.RAILS_MASTER_KEY }} 65 | TERRAFORM_PRE_RUN: | 66 | apt-get update 67 | apt-get install -y zip 68 | with: 69 | path: terraform 70 | var_file: terraform/production.tfvars 71 | add_github_comment: changes-only 72 | backend_config: > 73 | access_key=${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} 74 | secret_key=${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} 75 | bucket=${{ secrets.TERRAFORM_STATE_BUCKET_NAME }} 76 | key=terraform.tfstate.production 77 | 78 | # Uncomment this step if you need to debug issues 79 | # with mismatched app checksum between plan and apply 80 | # - name: Save app zip for debugging 81 | # uses: actions/upload-artifact@v4 82 | # with: 83 | # name: app-src-plan 84 | # path: terraform/dist/src.zip 85 | # compression-level: 0 86 | # retention-days: 1 87 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/terraform-staging.yml: -------------------------------------------------------------------------------- 1 | name: Run Terraform plan in staging 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | 11 | jobs: 12 | build-assets: 13 | name: Compile and clean assets 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Compile assets 18 | uses: ./.github/actions/compile-assets 19 | with: 20 | rails_env: staging 21 | # you may want to enable the next line to surface issues with missing assets, 22 | # but not until after you've deployed once and the cache has been created 23 | # fail_on_missing_cache: true 24 | - name: Upload assets 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: staging-assets 28 | path: public/assets 29 | 30 | terraform: 31 | name: Terraform plan 32 | runs-on: ubuntu-latest 33 | needs: build-assets 34 | environment: staging 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | - name: terraform validate 42 | uses: dflook/terraform-validate@v1 43 | with: 44 | path: terraform 45 | 46 | - name: terraform fmt 47 | uses: dflook/terraform-fmt-check@v1 48 | with: 49 | path: terraform 50 | 51 | - name: Download assets 52 | uses: actions/download-artifact@v4 53 | with: 54 | name: staging-assets 55 | path: public/assets 56 | 57 | - name: terraform plan 58 | uses: dflook/terraform-plan@v1 59 | env: 60 | CF_API_URL: "https://api.fr.cloud.gov" 61 | CF_USER: ${{ secrets.CF_USERNAME }} 62 | CF_PASSWORD: ${{ secrets.CF_PASSWORD }} 63 | TF_VAR_cf_user: ${{ secrets.CF_USERNAME }} 64 | TF_VAR_rails_master_key: ${{ secrets.RAILS_MASTER_KEY }} 65 | TERRAFORM_PRE_RUN: | 66 | apt-get update 67 | apt-get install -y zip 68 | with: 69 | path: terraform 70 | var_file: terraform/staging.tfvars 71 | add_github_comment: changes-only 72 | backend_config: > 73 | access_key=${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} 74 | secret_key=${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }} 75 | bucket=${{ secrets.TERRAFORM_STATE_BUCKET_NAME }} 76 | key=terraform.tfstate.staging 77 | 78 | # Uncomment this step if you need to debug issues 79 | # with mismatched app checksum between plan and apply 80 | # - name: Save app zip for debugging 81 | # uses: actions/upload-artifact@v4 82 | # with: 83 | # name: app-src-plan 84 | # path: terraform/dist/src.zip 85 | # compression-level: 0 86 | # retention-days: 1 87 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/github_actions/templates/github/workflows/validate-ssp.yml: -------------------------------------------------------------------------------- 1 | name: Validate OSCAL Assembly 2 | 3 | on: [pull_request] 4 | 5 | permissions: 6 | contents: read 7 | pull-requests: write 8 | 9 | jobs: 10 | validate_ssp: 11 | name: Validate SSP format 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Validate SSP 17 | uses: ./.github/actions/trestle-cmd 18 | with: 19 | cmd: validate-ssp-json 20 | 21 | check_ssp: 22 | name: Check assembly is current 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Check assembly 28 | uses: ./.github/actions/trestle-cmd 29 | with: 30 | cmd: assemble-ssp-json 2> /dev/null | grep "^No changes to assembled ssp" 31 | 32 | - name: Comment on pull request 33 | if: failure() 34 | uses: actions/github-script@v7 35 | with: 36 | script: | 37 | const output = `SSP assembly detected changes that aren't checked in. 38 | 39 | Run \`bin/trestle assemble-ssp-json\` to ensure markdown changes are reflected in your SSP`; 40 | 41 | github.rest.issues.createComment({ 42 | issue_number: context.issue.number, 43 | owner: context.repo.owner, 44 | repo: context.repo.repo, 45 | body: output 46 | }); 47 | -------------------------------------------------------------------------------- /lib/generators/rails_template18f/gitlab_ci/gitlab_ci_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators" 4 | 5 | module RailsTemplate18f 6 | module Generators 7 | class GitlabCiGenerator < ::Rails::Generators::Base 8 | include Base 9 | include CloudGovOptions 10 | 11 | class_option :node_version, desc: "Node version to test against in actions" 12 | class_option :postgres_version, default: "15", desc: "PostgreSQL version " 13 | 14 | desc <<~DESC 15 | Description: 16 | Install GitLab CI workflow files 17 | DESC 18 | 19 | def install_actions 20 | template "gitlab-ci.yml", ".gitlab-ci.yml" 21 | directory "gitlab", ".gitlab" 22 | end 23 | 24 | def update_readme 25 | if file_content("README.md").match?(/^## CI\/CD$/) 26 | insert_into_file "README.md", readme_cicd, after: "## CI/CD\n" 27 | insert_into_file "README.md", readme_staging_deploy, after: "#### Staging\n" 28 | insert_into_file "README.md", readme_prod_deploy, after: "#### Production\n" 29 | insert_into_file "README.md", readme_credentials, after: "#### Credentials and other Secrets\n" 30 | else 31 | append_to_file "README.md", <<~EOM 32 | ## CI/CD 33 | #{readme_cicd} 34 | 35 | ### Deployment 36 | 37 | #### Staging 38 | #{readme_staging_deploy} 39 | 40 | #### Production 41 | #{readme_prod_deploy} 42 | 43 | #### Credentials and other Secrets 44 | #{readme_credentials} 45 | EOM 46 | end 47 | end 48 | 49 | def update_boundary_diagram 50 | boundary_filename = "doc/compliance/apps/application.boundary.md" 51 | insert_into_file boundary_filename, <