Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IDP integration #4190

Draft
wants to merge 15 commits into
base: stable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ untracked
docker-compose.dev.yml
yarn-error.log
/dev/minio/*
/dev/auth/certs/*

# docker deployment
docker/deploy/REVISION
Expand Down
43 changes: 43 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,49 @@ class ApplicationController < ActionController::Base

prepend_before_action :skip_timeout

# if we have a jwt, use it,
# sign-in user (programmatically)
# if not, call devise authenticate_user!
def authenticate_user!
if valid_jwt?
login_jwt_user
else
super
end
end

private def valid_jwt?
return false unless ENV['JWT_SECRET']
return false unless jwt_payload['email_verified'] == true
return false unless jwt_payload['email'] == request.headers['HTTP_X_FORWARDED_USER']
return false unless jwt_payload['exp'] > Time.now.to_i

# TODO: any additional checks we should do here? Also, I'd rather invert this logic and return true
# if everything looks good, but false if we can't positively verify

true
end

private def jwt_payload
# FIXME: this needs to validate the cookie (false should be true)
@jwt_payload ||= JWT.decode(request.headers['HTTP_X_FORWARDED_ACCESS_TOKEN'], ENV['JWT_SECRET'], false, algorithm: 'RS256').first
end

private def login_jwt_user
email = request.headers['HTTP_X_FORWARDED_USER']
return unless email

# TODO: should this potentially do a find_or_create_by?
user = User.find_by(email: email.downcase)
return unless user

# TODO: Need to check expiration/re-request a token so we aren't signed out
# after 30 minutes if we're active
return if user_signed_in?

sign_in(user)
end

private def resource_name
:user
end
Expand Down
3 changes: 3 additions & 0 deletions app/controllers/root_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
class RootController < ApplicationController
skip_before_action :authenticate_user!
def index
# Handle oauth2-proxy logins
login_jwt_user if valid_jwt? && ! user_signed_in?

# custom_content = lookup_context.exists?('homepage_content', ['root'], true)
return unless current_user

Expand Down
7 changes: 6 additions & 1 deletion app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ def create
def destroy
request.env['last_user'] = current_user

super
if request.headers['HTTP_X_FORWARDED_USER']
sign_out(current_user)
redirect_to '/oauth2/sign_out'
else
super
end
end

def keepalive
Expand Down
1 change: 1 addition & 0 deletions config/initializers/jwt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
JWT.configuration.strict_base64_decoding = true
51 changes: 51 additions & 0 deletions dev/auth/dex.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# https://github.com/oauth2-proxy/oauth2-proxy/blob/master/contrib/local-environment/dex.yaml
# This configuration is intended to be used with the docker-compose testing
# environment.
# This should configure Dex to run on port 4443 and provides a static login
issuer: http://dex.dev.test:4443/dex
storage:
type: etcd
config:
endpoints:
- http://etcd:2379
namespace: dex/
web:
http: 0.0.0.0:4443
# https: 0.0.0.0:4443
# tlsCert: /etc/certs/dev.test.crt
# tlsKey: /etc/certs/dev.test.key
# tlsClientCA: /etc/certs/root-ca.pem
# headers:
# X-Frame-Options: "DENY"
# X-Content-Type-Options: "nosniff"
# X-XSS-Protection: "1; mode=block"
# Content-Security-Policy: "default-src 'self'"
# Strict-Transport-Security: "max-age=31536000; includeSubDomains"
oauth2:
skipApprovalScreen: true
expiry:
signingKeys: "4h"
idTokens: "1h"
staticClients:
- id: hmis-warehouse
redirectURIs:
# These redirect URIs point to the `--redirect-url` for OAuth2 proxy.
- 'http://hmis-warehouse.dev.test/oauth2/callback' # For basic proxy example.
# - 'http://oauth2-proxy.oauth2-proxy.localhost/oauth2/callback' # For nginx and traefik example.
name: 'HMIS Warehouse'
secret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
enablePasswordDB: true
staticPasswords:
- email: "[email protected]"
# bcrypt hash of the string "password"
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
- email: "[email protected]"
# bcrypt hash of the string "password"
# bash $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
# hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
# bash $(echo 'fix-this-password-55-*' | htpasswd -BinC 10 admin | cut -d: -f2)
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin-user"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
29 changes: 29 additions & 0 deletions dev/auth/oauth2-proxy.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
http_address="0.0.0.0:4180"
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
email_domains="*"
# cookie_secure="true"
# force_https="true"
# https_address="oauth2-proxy.dev.test:4180"
# upstreams=["http://httpbin.localtest.me"]
upstreams=["http://warehouse.dev.test:3000"]
cookie_domains=[".dev.test", "oauth2-proxy.dev.test", "hmis-warehouse.dev.test"] # Required so cookie can be read on all subdomains.
whitelist_domains=[".dev.test", "oauth2-proxy.dev.test", "hmis-warehouse.dev.test"] # Required to allow redirection back to original requested target.
pass_access_token="true"
set_xauthrequest="true"
prefer_email_to_user="true"
pass_user_headers="true"
# tls_cert_file="/etc/certs/dev.test.crt"
# tls_key_file="/etc/certs/dev.test.key"
# provider_ca_files="/etc/certs/root-ca.pem"

# dex provider
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
client_id="hmis-warehouse"
redirect_url="http://hmis-warehouse.dev.test/oauth2/callback"

oidc_issuer_url="http://dex.dev.test:4443/dex"
provider="oidc"
provider_display_name="Dex"

# These paths don't require authentication
skip_auth_routes=["public_agencies", "public_files/*"]
71 changes: 61 additions & 10 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,6 @@ services:
stdin_open: true
tty: true
command: bin/rails server -b 0.0.0.0
labels:
- traefik.enable=${TRAEFIK_ENABLED:-false}
- traefik.http.routers.op.entrypoints=web
- traefik.http.routers.op.rule=Host(`${FQDN:-hmis-warehouse.dev.test}`)
- traefik.http.services.op_https.loadbalancer.server.port=3000
- traefik.http.routers.op_https.rule=Host(`${FQDN:-hmis-warehouse.dev.test}`)
- traefik.http.routers.op_https.tls=true
- traefik.http.routers.op_https.entrypoints=web-secure
- traefik.http.middlewares.op_https.redirectscheme.scheme=https
- traefik.http.routers.op.middlewares=op_https
ports:
- "3000"
- "9394"
Expand All @@ -151,6 +141,9 @@ services:
condition: service_started
yarn:
condition: service_started
oauth2-proxy:
condition: service_started
hostname: warehouse.dev.test

# To transition from a previous major version to pg13, run ./docker/pg13/upgrade.db
db_previous:
Expand Down Expand Up @@ -262,6 +255,64 @@ services:
- 9000:9000
- 9001:9001 # enable for UI access
command: server --certs-dir /certs /data
# Access http://oauth2-proxy.dev.test:4180 to initiate a login cycle
# some useful notes about setting up SSL certs
# mkcert -install
# mkcert dev.test "*.dev.test" localhost 127.0.0.1 ::1
# mkcert -CACERT
# copy the certs and CA cert to dev/auth/certs/
oauth2-proxy:
container_name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
command: --config /oauth2-proxy.cfg
hostname: hmis-warehouse.dev.test
volumes:
- "./dev/auth/oauth2-proxy.cfg:/oauth2-proxy.cfg"
# - "./dev/auth/certs/dev.test.crt:/etc/certs/dev.test.crt"
# - "./dev/auth/certs/dev.test.key:/etc/certs/dev.test.key"
# - "./dev/auth/certs/ca/rootCA.pem:/etc/certs/root-ca.pem"
labels:
- traefik.enable=${TRAEFIK_ENABLED:-false}
- traefik.http.routers.op.entrypoints=web
- traefik.http.routers.op.rule=Host(`${FQDN:-hmis-warehouse.dev.test}`)
- traefik.http.services.op_https.loadbalancer.server.port=4180
- traefik.http.routers.op_https.rule=Host(`${FQDN:-hmis-warehouse.dev.test}`)
- traefik.http.routers.op_https.tls=true
- traefik.http.routers.op_https.entrypoints=web-secure
- traefik.http.middlewares.op_https.redirectscheme.scheme=https
- traefik.http.routers.op.middlewares=op_https
restart: unless-stopped
ports:
- 4180:4180/tcp
depends_on:
- dex
dex:
container_name: dex
# image: dexidp/dex:latest-alpine
image: ghcr.io/dexidp/dex:v2.39.0
command: dex serve /dex.yaml
hostname: dex
volumes:
- "./dev/auth/dex.yaml:/dex.yaml"
# - "./dev/auth/certs/dev.test.crt:/etc/certs/dev.test.crt"
# - "./dev/auth/certs/dev.test.key:/etc/certs/dev.test.key"
# - "./dev/auth/certs/ca/rootCA.pem:/etc/certs/root-ca.pem"
restart: unless-stopped
ports:
- 4443:4443/tcp
networks:
default:
aliases:
- dex.dev.test
depends_on:
- etcd
etcd:
container_name: etcd
image: gcr.io/etcd-development/etcd:v3.5.13
entrypoint: /usr/local/bin/etcd
command:
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd:2379

volumes:
bundle_alpine:
Expand Down
Loading