Skip to content

Commit

Permalink
Merge pull request #444 from 18F/stages/rc-2024-03-19
Browse files Browse the repository at this point in the history
Deploy RC 69 to production
  • Loading branch information
matthinz authored Mar 19, 2024
2 parents 850b4e3 + 52fe408 commit b12342b
Show file tree
Hide file tree
Showing 16 changed files with 136 additions and 88 deletions.
29 changes: 29 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!-- Uncomment and update the sections you need for your PR! -->

<!--
## 🎫 Ticket
Link to the relevant ticket.
-->

<!--
## 🛠 Summary of changes
Write a brief description of what you changed.
-->

<!--
## 📜 Testing Plan
Provide a list of steps to confirm the changes.
1. Step 1
2. Step 2
3. Step 3
-->

<!--
## 👀 Screenshots
If relevant, include a screenshot or screen capture of the changes.
-->
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@

/postgres-data

# Avoid hard-pinning Homebrew dependencies
Brewfile.lock.json

# Ignore the files generated during setup
/config/local-certs/rootCA.key
/config/local-certs/rootCA.pem
Expand Down
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ check_expiring_certificates:
script:
- *bundle_install
- bundle exec rake db:setup --trace
- bundle exec rake certs:print_expiring
- bundle exec rake certs:print_expiring[0]

# Build a container image async, and don't block CI tests
# Cache intermediate images for 1 week (168 hours)
Expand Down
3 changes: 3 additions & 0 deletions Brewfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
brew 'nginx'
brew 'postgresql@14'
brew '[email protected]'
103 changes: 32 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,100 +7,61 @@ PIV/CAC support for login.gov.

#### Dependencies

- Ruby 3.0
- OpenSSL 1.1
- [Postgresql](http://www.postgresql.org/download/)
- Ruby 3.2
- OpenSSL 1.1 (see [troubleshooting notes](#troubleshooting-openssl-or-certificate-validation-errors))
- [PostgreSQL](http://www.postgresql.org/download/)
- Nginx

#### Setting up and running the app

1. Make sure you have a working development environment with all the
[dependencies](#dependencies) installed. On OS X, the easiest way
to set up a development environment is by running our [Laptop]
script. The script will install all of this project's dependencies.
Run the following command to set up your local environment:

If using rbenv, you may need to alias your specific installed ruby
version to the more generic version found in the `.ruby-version` file.
To do this, use [`rbenv-aliases`](https://github.com/tpope/rbenv-aliases):
```
$ make setup
```

```
git clone git://github.com/tpope/rbenv-aliases.git "$(rbenv root)/plugins/rbenv-aliases" # install rbenv-aliases per its documentation
rbenv alias 3.0 3.0.6 # create the version alias
```

1. Make sure you have Nginx installed.

```
$ brew install nginx
```

1. Make sure Postgres is running.

For example, if you've installed the laptop script on OS X, you can start the services like this:

```
$ brew services start postgresql
```

1. Create the development and test databases:

```
$ psql -c "CREATE DATABASE identity_pki_dev;"
$ psql -c "CREATE DATABASE identity_pki_test;"
```

1. Run the following command to set up the environment
The first time, it will prompt for a passphrase for the root certificate. You can put anything as long as you remember it, it's just for development. You will also want to make sure to [trust the root SSL certificate](#trust-the-root-ssl-certificate)

- The first time, it will prompt for a passphrase for the root certificate. You can put anything as long as you remember it, it's just for development. To keep it simple, try `salty pickles`.
Run the app server with:

- Make sure to [trust the root SSL certificate](#trust-the-root-ssl-certificate)

```
$ make setup
```
```
$ make run
```

This command copies sample configuration files, installs required gems
and sets up the database.
### Running the app locally with the IDP

1. Run the app server with:
#### Trust the root SSL certificate

```
$ make run
```
Most of the root certificate management is handled by `bin/setup` but there are some manual steps

Before making any commits, you'll also need to run `overcommit --sign.`
This verifies that the commit hooks defined in our `.overcommit.yml` file are
the ones we expect. Each change to the `.overcommit.yml` file, including the initial install
performed in the setup script, will necessitate a new signature.
1. Open the Keychain Access app

For more information, see [overcommit](https://github.com/brigade/overcommit)
2. Within your default keychain, search for the certificate "**identity-pki Development Certificate**"

If you want to measure the app's performance in development, set the
`rack_mini_profiler` option to `'on'` in `config/application.yml` and
restart the server. See the [rack_mini_profiler] gem for more details.
3. Double-click the certificate to view its details

[Laptop]: https://github.com/18F/laptop
[rack_mini_profiler]: https://github.com/MiniProfiler/rack-mini-profiler
4. Expand the "Trust" section and select "Always Trust" for the top-level "When using this certificate" dropdown

### Running the app locally with the IDP
5. Close the details to save your changes. You'll be prompted to enter your password or PIN to confirm the changes.

#### Trust the root SSL certificate
#### Troubleshooting OpenSSL or certificate validation errors

Most of the root certificate management is handled by `bin/setup` but there are some manual steps
Errors commonly occur due to a mismatch in the expected version of OpenSSL:

1. Open the Keychain Access app
- Trying to register or authenticate with your PIV locally produces errors saying your certificate is invalid
- Running certificate Rake tasks produces Ruby errors

2. Go to "Certificates" bottom left section
If you encounter errors, ensure that you have OpenSSL 1.1 installed, including bindings for your Ruby installation.

3. Find the cert named "**identity-pki Development Certificate**" open its settings
To check, run: `ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'`

4. Under the "Trust" section, select "Always Trust" for the top-level "When using this certificate" dropdown
If the version is anything other than OpenSSL 1.1, you will need to install OpenSSL 1.1 and reinstall Ruby with the OpenSSL directory pointing to the correct version.

#### Troubleshooting Certificate invalid Error
If you are attempting to register or sign in with your PIV locally and you get errors saying your Certificate is invalid, Ensure that you have OpenSSl 1.1 installed and running for ruby.
To check run `ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'`.
If you have [Homebrew](https://brew.sh/) available and use [`rbenv`](https://github.com/rbenv/rbenv) to manage your Ruby installation, you can run the following command to reinstall the project's version of Ruby with OpenSSL 1.1:

If you notice that the version is not valid you will need to install [email protected] and reinstall ruby with the openssl directory pointing to the correct version.
```
RUBY_CONFIGURE_OPTS=--with-openssl-dir=$(brew --prefix [email protected]) rbenv install
```

#### Cleaning up the root SSL certificate

Expand Down
3 changes: 3 additions & 0 deletions app/controllers/identify_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ def log_certificate(cert)
}

attributes.delete(:issuer) if validation_result == 'self-signed cert'
if valid
attributes[:matched_policy_oids] = cert.matched_policy_oids.map { |oid| [oid, true] }.to_h
end

# Log certificate if it fails either OpenSSL validation, but passes our current validation or vice versa
if valid != login_certs_openssl_result[:valid] || valid != ficam_certs_openssl_result[:valid]
Expand Down
2 changes: 1 addition & 1 deletion app/models/certificate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(x509_cert)
def_delegators :x509_cert, :not_before, :not_after, :subject, :issuer, :verify,
:public_key, :serial, :to_text

def_delegators :@cert_policies, :allowed_by_policy?, :critical_policies_recognized?
def_delegators :@cert_policies, :allowed_by_policy?, :critical_policies_recognized?, :matched_policy_oids

def trusted_root?
CertificateStore.trusted_ca_root_identifiers.include?(key_id)
Expand Down
6 changes: 5 additions & 1 deletion app/policies/certificate_policies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ def allowed_by_policy?
# otherwise, we want to allow it for now, but log the cert so we can see what policies are
# coming up
# This policy check is only on the leaf certificate - not used by CAs
matched_policy_oids.any?
end

def matched_policy_oids
mapping = PolicyMappingService.new(@certificate).call
expected_policies = required_policies
cert_policies = policies.map { |policy| mapping[policy] }
(cert_policies & expected_policies).any?
(cert_policies & expected_policies)
end

def policies
Expand Down
2 changes: 2 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ chdir APP_ROOT do
File.write('config/application.yml', default_application_yml.to_yaml) unless File.exist?('config/application.yml')

puts '== Installing dependencies =='
brew_installed = system "brew -v 2>&1"
run "brew bundle" if brew_installed
system! 'gem install bundler --conservative'
run 'gem install foreman --conservative && gem update foreman'
system('bundle check') || system!('bundle install --without deploy production')
Expand Down
10 changes: 7 additions & 3 deletions k8.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ ENV TZ=Etc/UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# Install dependencies
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y \
build-essential \
cron \
curl \
gettext-base \
git-core \
tar \
unzip \
Expand Down Expand Up @@ -95,7 +96,10 @@ RUN ln -s /usr/local/nginx/nginx /usr/local/sbin/nginx; \
COPY --chmod=644 ./k8files/status-map.conf /opt/nginx/conf/
COPY --chmod=644 ./k8files/nginx.conf /opt/nginx/conf/
COPY --chmod=644 ./k8files/status.conf /opt/nginx/conf/sites.d/
COPY ./k8files/pivcac.conf /opt/nginx/conf/sites.d/
COPY ./k8files/pivcac.conf /opt/nginx/conf/sites.d/pivcac.conftemp

# Download RDS Combined CA Bundles
RUN wget -P /usr/local/share/aws/ https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem

# Create cron jobs
RUN echo '* */4 * * * websrv flock -n /tmp/update_cert_revocations.lock -c /usr/local/bin/update_cert_revocations' > /etc/cron.d/update_cert_revocations; \
Expand Down Expand Up @@ -142,7 +146,7 @@ RUN mkdir -p ${RAILS_ROOT}/keys; chmod -R 0755 ${RAILS_ROOT}/keys; \
mkdir -p ${RAILS_ROOT}/config/puma; chmod -R 0755 ${RAILS_ROOT}/config/puma;
COPY --chown=app --chmod=755 ./k8files/application.yml.default.docker ./config/application.yml
COPY --chown=app --chmod=755 ./k8files/newrelic.yml ./config/newrelic.yml
COPY --chown=app --chmod=755 ./k8files/puma_production ./config/puma/production.rb
COPY --chown=app --chmod=755 ./k8files/puma_production ./config/puma/production.rbtemp

# Expose port the app runs on
EXPOSE 443
Expand Down
28 changes: 26 additions & 2 deletions k8files/configure_environment
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ else
AWS_ACCOUNT_NUM="894947205914"
fi

# Set variables used to configure NGINX, SSL Certs, and Puma configuration

if [[ "${ENV}" = "reviewapps" ]]; then
export ENV_CONFIG_PIVCAC_SSL_DOMAIN="${CERT_ENV}.pivcac.identitysandbox.gov"
export ENV_CONFIG_NGINX_SERVER_NAME="*.${ENV_CONFIG_PIVCAC_SSL_DOMAIN}"
CERT_DOMAIN="-d *.${ENV_CONFIG_PIVCAC_SSL_DOMAIN}"
else
export ENV_CONFIG_PIVCAC_SSL_DOMAIN="pivcac.${CERT_ENV}.identitysandbox.gov"
export ENV_CONFIG_NGINX_SERVER_NAME="*.${ENV_CONFIG_PIVCAC_SSL_DOMAIN}"
CERT_DOMAIN="-d ${ENV_CONFIG_PIVCAC_SSL_DOMAIN} -d *.${ENV_CONFIG_PIVCAC_SSL_DOMAIN}"
fi

if [[ -z "${CERT_DOMAIN:-}" ]]; then
echo "Certificate Domain did not get set properly. This error should not happen. Troubleshoot script"
exit 1
fi

# Configure nginx
sudo chmod og+w /opt/nginx/conf/nginx.conf
sudo chmod og+w /opt/nginx/conf
Expand Down Expand Up @@ -70,14 +87,21 @@ sudo -E -u root chown root: /etc/cron.d/update_letsencrypt_certs
sudo -E -u root chmod 700 /etc/cron.d/update_letsencrypt_certs

# Check if bundle exists/is not null
sudo -E -u root [ -s /root/letsencrypt.${CERT_ENV}.tar.gz ] && sudo -E -u root tar zxf /root/letsencrypt.${CERT_ENV}.tar.gz -C /etc || sudo -E -u root certbot certonly --agree-tos -n --dns-route53 -d *.${CERT_ENV}.pivcac.identitysandbox.gov --email [email protected] --server https://acme-v02.api.letsencrypt.org/directory --deploy-hook "/usr/local/bin/push_letsencrypt_certs.sh -e ${ENV} -c ${CERT_ENV}" --preferred-chain 'ISRG Root X1' --key-type rsa --rsa-key-size 2048
sudo -E -u root [ -s /root/letsencrypt.${CERT_ENV}.tar.gz ] && sudo -E -u root tar zxf /root/letsencrypt.${CERT_ENV}.tar.gz -C /etc || sudo -E -u root certbot certonly --agree-tos -n --dns-route53 ${CERT_DOMAIN} --email [email protected] --server https://acme-v02.api.letsencrypt.org/directory --deploy-hook "/usr/local/bin/push_letsencrypt_certs.sh -e ${ENV} -c ${CERT_ENV}" --preferred-chain 'ISRG Root X1' --key-type rsa --rsa-key-size 2048

sudo -E -u root certbot renew -n --deploy-hook "/usr/local/bin/push_letsencrypt_certs.sh -e ${ENV} -c ${CERT_ENV}" --preferred-chain 'ISRG Root X1' --key-type rsa --rsa-key-size 2048

# Update Letsencrypt folder permissions
sudo -E -u root chmod 755 /etc/letsencrypt/live
sudo -E -u root chmod 755 /etc/letsencrypt/archive
sudo -E -u root chmod -R 755 /etc/letsencrypt/archive/review-app.pivcac.identitysandbox.gov/*
sudo -E -u root chmod -R 755 /etc/letsencrypt/archive/${ENV_CONFIG_PIVCAC_SSL_DOMAIN}/*

# Set Environment configuration in file
envsubst '${ENV_CONFIG_PIVCAC_SSL_DOMAIN} ${ENV_CONFIG_NGINX_SERVER_NAME}' < /opt/nginx/conf/sites.d/pivcac.conftemp > ./pivcac.conftemp && \
sudo mv ./pivcac.conftemp /opt/nginx/conf/sites.d/pivcac.conf
envsubst '${ENV_CONFIG_PIVCAC_SSL_DOMAIN}' < /app/config/puma/production.rbtemp > /app/config/puma/production.rb
sudo rm -f /opt/nginx/conf/sites.d/pivcac.conftemp
sudo rm -f /app/config/puma/production.rbtempd

# Start nginx
sudo -E -u root nginx
Expand Down
7 changes: 3 additions & 4 deletions k8files/pivcac.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ add_header Strict-Transport-Security $sts_value always;

server {
listen 443 ssl;
server_name *.review-app.pivcac.identitysandbox.gov;
server_name ${ENV_CONFIG_NGINX_SERVER_NAME};


ssl_certificate /etc/letsencrypt/live/review-app.pivcac.identitysandbox.gov/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/review-app.pivcac.identitysandbox.gov/privkey.pem;
ssl_certificate /etc/letsencrypt/live/${ENV_CONFIG_PIVCAC_SSL_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${ENV_CONFIG_PIVCAC_SSL_DOMAIN}/privkey.pem;
ssl_verify_client optional_no_ca; # on;
ssl_verify_depth 10;

Expand Down
2 changes: 1 addition & 1 deletion k8files/puma_production
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ state_path "#{app_dir}/tmp/pids/puma.state"


set_remote_address proxy_protocol: :v1
bind "ssl://0.0.0.0:3001?key=/etc/letsencrypt/live/review-app.pivcac.identitysandbox.gov/privkey.pem&cert=/etc/letsencrypt/live/review-app.pivcac.identitysandbox.gov/fullchain.pem"
bind "ssl://0.0.0.0:3001?key=/etc/letsencrypt/live/${ENV_CONFIG_PIVCAC_SSL_DOMAIN}/privkey.pem&cert=/etc/letsencrypt/live/${ENV_CONFIG_PIVCAC_SSL_DOMAIN}/fullchain.pem"
17 changes: 16 additions & 1 deletion k8files/update_letsencrypt_certs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,27 @@ else
AWS_ACCOUNT_NUM="894947205914"
fi

# Set domains that PIVCAC cert will be requested for. Review-app environment uses a different domain structure
# than other environments, so we test to see if ENV is reviewapp to set it's value, otherwise the domain follows
# standard PIVCAC domain structure.
CERT_DOMAIN=""
if [[ "${ENV}" = "reviewapp" ]]; then
CERT_DOMAIN="-d *.${CERT_ENV}.pivcac.identitysandbox.gov"
else
CERT_DOMAIN="-d pivcac.${CERT_ENV}.identitysandbox.gov -d *.pivcac.${CERT_ENV}.identitysandbox.gov"
fi

if [[ -z "${CERT_DOMAIN:-}" ]]; then
echo "Certificate Domain did not get set properly. This error should not happen. Troubleshoot script"
exit 1
fi

[ -e /root/letsencrypt.${CERT_ENV}.tar.gz ] && rm -f /root/letsencrypt.${CERT_ENV}.tar.gz

aws s3 cp s3://login-gov-pivcac-${ENV}.${AWS_ACCOUNT_NUM}-us-west-2/letsencrypt.${CERT_ENV}.tar.gz /root/letsencrypt.${CERT_ENV}.tar.gz

cd /etc/letsencrypt

[ -s /root/letsencrypt.${CERT_ENV}.tar.gz ] && tar zxf /root/letsencrypt.${CERT_ENV}.tar.gz || certbot certonly --agree-tos -n --dns-route53 -d *.${CERT_ENV}.pivcac.identitysandbox.gov --email [email protected] --server https://acme-v02.api.letsencrypt.org/directory --deploy-hook "/usr/local/bin/push_letsencrypt_certs.sh -e ${ENV} -c ${CERT_ENV}" --preferred-chain 'ISRG Root X1' --key-type rsa --rsa-key-size 2048
[ -s /root/letsencrypt.${CERT_ENV}.tar.gz ] && tar zxf /root/letsencrypt.${CERT_ENV}.tar.gz || certbot certonly --agree-tos -n --dns-route53 ${CERT_DOMAIN} --email [email protected] --server https://acme-v02.api.letsencrypt.org/directory --deploy-hook "/usr/local/bin/push_letsencrypt_certs.sh -e ${ENV} -c ${CERT_ENV}" --preferred-chain 'ISRG Root X1' --key-type rsa --rsa-key-size 2048

certbot renew -n --deploy-hook "/usr/local/bin/push_letsencrypt_certs.sh -e ${ENV} -c ${CERT_ENV}" --preferred-chain 'ISRG Root X1' --key-type rsa --rsa-key-size 2048
5 changes: 3 additions & 2 deletions lib/tasks/certs.rake
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ namespace :certs do
end

desc 'Print expiring certs'
task print_expiring: :environment do
deadline = 30.days.from_now
task :print_expiring, [:deadline_days] => [:environment] do |t, args|
args.with_defaults(:deadline_days => 30)
deadline = args[:deadline_days].to_i.days.from_now

cert_store = CertificateStore.instance
cert_store.load_certs!(dir: Rails.root.join('config/certs'))
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/identify_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,11 @@
openssl_errors: 'error 20 at 0 depth lookup: unable to get local issuer certificate',
ficam_openssl_valid: false,
ficam_openssl_errors: 'error 20 at 0 depth lookup: unable to get local issuer certificate',
matched_policy_oids: { '2.16.840.1.101.2.1.11.9' => true },
}.to_json).once

@request.headers['X-Client-Cert'] = CGI.escape(client_cert_pem)


get :create, params: { nonce: '123', redirect_uri: 'http://example.com/' }
expect(response).to have_http_status(:found)
expect(response.has_header?('Location')).to be_truthy
Expand Down

0 comments on commit b12342b

Please sign in to comment.