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

Support Dex behind Apache reverse proxy [release_2.0] #2183

Merged
merged 7 commits into from
Aug 1, 2022
Merged
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
34 changes: 27 additions & 7 deletions ood-portal-generator/lib/ood_portal_generator/dex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ def initialize(opts = {}, view)
@dex_config[:issuer] = issuer
@dex_config[:storage] = storage
@dex_config[:web] = {
http: "0.0.0.0:#{http_port}",
http: "#{listen}:#{http_port}",
}
@dex_config[:web][:https] = "0.0.0.0:#{https_port}" if ssl?
@dex_config[:web][:https] = "#{listen}:#{https_port}" if ssl?
@dex_config[:web][:tlsCert] = tls_cert unless tls_cert.nil?
@dex_config[:web][:tlsKey] = tls_key unless tls_key.nil?
copy_ssl_certs
Expand Down Expand Up @@ -71,12 +71,14 @@ def self.config_dir

private

def ssl?
@config.fetch(:ssl, !@view.ssl.nil?)
def listen
return 'localhost' unless @view.dex_uri.nil?
@config.fetch(:listen, '0.0.0.0')
end

def protocol
ssl? ? "https://" : "http://"
def ssl?
return false unless @view.dex_uri.nil?
@config.fetch(:ssl, !@view.ssl.nil?)
end

def servername
Expand All @@ -103,8 +105,25 @@ def tls_key
@tls_key ||= @config.fetch(:tls_key, nil)
end

def issuer_protocol
return 'https://' if !issuer_uri.empty? && !@view.ssl.nil?
return 'http://' if !issuer_uri.empty? && @view.ssl.nil?
ssl? ? "https://" : "http://"
end

def issuer_uri
@view.dex_uri.nil? ? '' : @view.dex_uri
end

def issuer_port
return '' if issuer_protocol == 'https://' && @view.port.to_s == '443' && !issuer_uri.empty?
return '' if issuer_protocol == 'http://' && @view.port.to_s == '80' && !issuer_uri.empty?
return ":#{@view.port}" if !issuer_uri.empty?
":#{port}"
end

def issuer
"#{protocol}#{servername}:#{port}"
"#{issuer_protocol}#{servername}#{issuer_port}#{issuer_uri}"
end

def storage
Expand Down Expand Up @@ -226,6 +245,7 @@ def copy_ssl_certs

def oidc_attributes
attrs = {
dex_http_port: http_port,
oidc_uri: '/oidc',
oidc_redirect_uri: client_redirect_uri,
oidc_provider_metadata_url: "#{issuer}/.well-known/openid-configuration",
Expand Down
6 changes: 4 additions & 2 deletions ood-portal-generator/lib/ood_portal_generator/view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
module OodPortalGenerator
# A view class that renders an OOD portal Apache configuration file
class View
attr_reader :ssl, :protocol, :servername, :proxy_server, :port
attr_accessor :user_map_match, :user_map_cmd, :logout_redirect
attr_reader :ssl, :protocol, :servername, :proxy_server, :port, :dex_uri
attr_accessor :user_map_match, :user_map_cmd, :logout_redirect, :dex_http_port
attr_accessor :oidc_uri, :oidc_client_secret, :oidc_remote_user_claim, :oidc_client_id, :oidc_provider_metadata_url, :oidc_redirect_uri
# @param opts [#to_h] the options describing the context used to render the
# template
Expand Down Expand Up @@ -93,6 +93,8 @@ def initialize(opts = {})
@register_root = opts.fetch(:register_root, nil)

servername = @servername || OodPortalGenerator.fqdn
@dex_uri = opts.fetch(:dex_uri, nil)
@dex_http_port = opts.fetch(:dex_http_port, nil)
@oidc_provider_metadata_url = opts.fetch(:oidc_provider_metadata_url, nil)
@oidc_client_id = opts.fetch(:oidc_client_id, nil)
@oidc_client_secret = opts.fetch(:oidc_client_secret, nil)
Expand Down
8 changes: 8 additions & 0 deletions ood-portal-generator/share/ood_portal_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@
# OIDCPassRefreshToken: On
# Default: {} (empty hash)

# The Dex URI behind Apache reverse proxy
# Setting this value to some path will result in Dex listening on localhost
# as well as only using HTTP for proxied communication
# Example:
# dex_uri: /dex
# Default: null
#dex_uri: null

# Dex configurations, values inside the "dex" structure are directly used to configure Dex
# If the value for "dex" key is false or null, Dex support is disabled
# Dex support will auto-enable if ondemand-dex package is installed
Expand Down
18 changes: 18 additions & 0 deletions ood-portal-generator/spec/application_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,24 @@
described_class.generate()
end

it 'generates full dex configs with Dex behind the Apache reverse proxy' do
allow(described_class).to receive(:context).and_return({
servername: 'example.com',
port: '443',
ssl: [
'SSLCertificateFile /etc/pki/tls/certs/example.com.crt',
'SSLCertificateKeyFile /etc/pki/tls/private/example.com.key',
'SSLCertificateChainFile /etc/pki/tls/certs/example.com-interm.crt',
],
dex_uri: '/dex',
})
expected_rendered = read_fixture('ood-portal.conf.dex-proxy')
expect(described_class.output).to receive(:write).with(expected_rendered)
expected_dex_yaml = read_fixture('dex.yaml.proxy').gsub('/etc/ood/dex', config_dir)
expect(described_class.dex_output).to receive(:write).with(expected_dex_yaml)
described_class.generate()
end

it 'generates full dex configs with SSL and multiple redirect URIs' do
allow(described_class).to receive(:context).and_return({
servername: 'example.com',
Expand Down
27 changes: 27 additions & 0 deletions ood-portal-generator/spec/fixtures/dex.yaml.proxy
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
issuer: https://example.com/dex
storage:
type: sqlite3
config:
file: "/etc/ood/dex/dex.db"
web:
http: localhost:5556
telemetry:
http: 0.0.0.0:5558
staticClients:
- id: example.com
redirectURIs:
- https://example.com/oidc
name: OnDemand
secret: 83bc78b7-6f5e-4010-9d80-22f328aa6550
oauth2:
skipApprovalScreen: true
enablePasswordDB: true
staticPasswords:
- email: ood@localhost
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: ood
userID: '08a8684b-db88-4b73-90a9-3cd1661f5466'
frontend:
dir: "/usr/share/ondemand-dex/web"
theme: ondemand
198 changes: 198 additions & 0 deletions ood-portal-generator/spec/fixtures/ood-portal.conf.dex-proxy
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#
# Open OnDemand Portal
#
# Generated using ood-portal-generator version 0.8.0
#
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !! !!
# !! DO NOT EDIT THIS FILE !!
# !! !!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#
# This file is auto-generated by ood-portal-generator and will be over-written
# in future updates.
#
# 1. To modify this file, first update the global configuration file:
#
# /etc/ood/config/ood_portal.yml
#
# You can find more information about the ood-portal-generator configuration
# at:
#
# https://osc.github.io/ood-documentation/latest/reference/commands/ood-portal-generator.html
#
# 2. Then build/install the updated Apache config with:
#
# sudo /opt/ood/ood-portal-generator/sbin/update_ood_portal
#
# 3. Finally, restart Apache to have the changes take effect:
#
# # For CentOS 6
# sudo service httpd24-httpd condrestart
# sudo service httpd24-htcacheclean condrestart
#
# # For CentOS 7
# sudo systemctl try-restart httpd24-httpd.service httpd24-htcacheclean.service
#
# # For CentOS 8
# sudo systemctl try-restart httpd.service htcacheclean.service
#


# Redirect all http traffic to the https Open OnDemand portal URI
# http://*:443
# #=> https://example.com:443
#
<VirtualHost *:80>
RewriteEngine On
RewriteRule ^(.*) https://example.com:443$1 [R=301,NE,L]
</VirtualHost>

# The Open OnDemand portal VirtualHost
#
<VirtualHost *:443>
ServerName example.com

ErrorLog "logs/example.com_error_ssl.log"
CustomLog "logs/example.com_access_ssl.log" combined

RewriteEngine On
RewriteCond %{HTTP_HOST} !^(example.com(:443)?)?$ [NC]
RewriteRule ^(.*) https://example.com:443$1 [R=301,NE,L]

# Support maintenance page during outages of OnDemand
RewriteEngine On
RewriteCond /var/www/ood/public/maintenance/index.html -f
RewriteCond /etc/ood/maintenance.enable -f
RewriteCond %{REQUEST_URI} !/public/maintenance/.*$
RewriteRule ^.*$ /public/maintenance/index.html [R=503,L]
ErrorDocument 503 /public/maintenance/index.html
Header Set Cache-Control "max-age=0, no-store"

Header always set Content-Security-Policy "frame-ancestors https://example.com;"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

SSLEngine On
SSLCertificateFile /etc/pki/tls/certs/example.com.crt
SSLCertificateKeyFile /etc/pki/tls/private/example.com.key
SSLCertificateChainFile /etc/pki/tls/certs/example.com-interm.crt

# OIDC configuration
#
OIDCProviderMetadataURL https://example.com/dex/.well-known/openid-configuration
OIDCClientID example.com
OIDCClientSecret 83bc78b7-6f5e-4010-9d80-22f328aa6550
OIDCRedirectURI https://example.com/oidc
OIDCRemoteUserClaim email
OIDCScope "openid profile email"
OIDCCryptoPassphrase 0caaf24ab1a0c33440c06afe99df986365b0781f
OIDCSessionInactivityTimeout 28800
OIDCSessionMaxDuration 28800
OIDCStateMaxNumberOfCookies 10 true
OIDCCookieSameSite Off

ProxyRequests Off
ProxyPreserveHost On
ProxyAddHeaders On
ProxyPass /dex http://localhost:5556/dex
ProxyPassReverse /dex http://localhost:5556/dex

# Lua configuration
#
LuaRoot "/opt/ood/mod_ood_proxy/lib"
LogLevel lua_module:info

# Log authenticated user requests (requires min log level: info)
LuaHookLog logger.lua logger

# Authenticated-user to system-user mapping configuration
#
SetEnv OOD_USER_MAP_MATCH "^([^@]+)@.*$"

# Per-user Nginx (PUN) configuration
# NB: Apache will need sudo privs to control the PUNs
#
SetEnv OOD_PUN_STAGE_CMD "sudo /opt/ood/nginx_stage/sbin/nginx_stage"

#
# Below is used for sub-uri's this Open OnDemand portal supports
#

# Serve up publicly available assets from local file system:
#
# https://example.com:443/public/favicon.ico
# #=> /var/www/ood/public/favicon.ico
#
Alias "/public" "/var/www/ood/public"
<Directory "/var/www/ood/public">
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>



# Reverse proxy traffic to backend PUNs through Unix domain sockets:
#
# https://example.com:443/pun/dev/app/simulations/1
# #=> unix:/path/to/socket|http://localhost/pun/dev/app/simulations/1
#
SetEnv OOD_PUN_URI "/pun"
<Location "/pun">
AuthType openid-connect
Require valid-user

ProxyPassReverse "http://localhost/pun"

# ProxyPassReverseCookieDomain implementation (strip domain)
Header edit* Set-Cookie ";\s*(?i)Domain[^;]*" ""

# ProxyPassReverseCookiePath implementation (less restrictive)
Header edit* Set-Cookie ";\s*(?i)Path\s*=(?-i)(?!\s*/pun)[^;]*" "; Path=/pun"

SetEnv OOD_PUN_SOCKET_ROOT "/var/run/ondemand-nginx"
SetEnv OOD_PUN_MAX_RETRIES "5"
LuaHookFixups pun_proxy.lua pun_proxy_handler

</Location>

# Control backend PUN for authenticated user:
# NB: See mod_ood_proxy for more details.
#
# https://example.com:443/nginx/stop
# #=> stops the authenticated user's PUN
#
SetEnv OOD_NGINX_URI "/nginx"
<Location "/nginx">
AuthType openid-connect
Require valid-user

LuaHookFixups nginx.lua nginx_handler
</Location>

# Redirect root URI to specified URI
#
# https://example.com:443/
# #=> https://example.com:443/pun/sys/dashboard
#
RedirectMatch ^/$ "/pun/sys/dashboard"

# Redirect logout URI to specified redirect URI
#
# https://example.com:443/logout
# #=> https://example.com:443/oidc?logout=https%3A%2F%2Fexample.com
#
Redirect "/logout" "/oidc?logout=https%3A%2F%2Fexample.com"

# OpenID Connect redirect URI:
#
# https://example.com:443/oidc
# #=> handled by mod_auth_openidc
#
<Location "/oidc">
AuthType openid-connect
Require valid-user
</Location>


</VirtualHost>
8 changes: 8 additions & 0 deletions ood-portal-generator/templates/ood-portal.conf.erb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ Listen <%= addr_port %>
<%= oidc_setting %> <%= @oidc_settings[oidc_setting] %>
<%- end -%>

<%- if @dex_uri -%>
ProxyRequests Off
ProxyPreserveHost On
ProxyAddHeaders On
ProxyPass <%= @dex_uri %> http://localhost:<%= @dex_http_port %><%= @dex_uri %>
ProxyPassReverse <%= @dex_uri %> http://localhost:<%= @dex_http_port %><%= @dex_uri %>

<%- end -%>
<%- end -%>
# Lua configuration
#
Expand Down
15 changes: 15 additions & 0 deletions spec/e2e/browser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,26 @@ def browser
browser.close
end

describe port(8080) do
it { is_expected.to be_listening.on('0.0.0.0').with('tcp') }
end

describe port(5556) do
it { is_expected.to be_listening }
end

it 'successfully loads dashboard no path' do
browser.goto ctr_base_url
expect(browser.title).to eq('Dashboard - Open OnDemand')
end

it 'has Dex issuer' do
on hosts, 'curl http://localhost:5556/.well-known/openid-configuration' do
data = JSON.parse(stdout)
expect(data['issuer']).to eq('http://localhost:5556')
end
end

it 'succesfully redirects /pun/sys/activejobs' do
browser.goto "#{ctr_base_url}/pun/sys/activejobs"
expect(browser.url).to eq("#{ctr_base_url}/pun/sys/dashboard/activejobs")
Expand Down
Loading