diff --git a/README.md b/README.md index fec4a6a..da71a04 100644 --- a/README.md +++ b/README.md @@ -76,16 +76,15 @@ This will generate a base64 encoded 4096-bit RSA private key that can be used in An example configuration file can be found in `example-config.yaml`. A breakdown of each option is below -- `auth_listen_port`: Port on which the authentication server listens. - `data_listen_port`: Port on which the data server listens. +- `metrics_listen_port`: Port where the metrics server should listen. - `service_url`: URL of the Veriflow service. - `cookie_secret`: Secret key used for cookie encryption and verification. - `cookie_settings`: Settings related to the session cookie set by Veriflow for each site. - `sameSite`: Sets the sameSite attribute of the cookie. Default "Lax" - `secure`: Sets whether the cookie should be secure. Default "false" + - `maxAge`: Sets the maximim time of the authenticated sessions in milliseconds (default 3 hours [3600000]) - `redis_connection_string`: Connection string for the redis server to connect to (e.g. redis://127.0.0.1:6379) -- `redis_host`: Hostname of the Redis database server. (deprecated, for backwards compatibility only) -- `redis_port`: Port of the Redis database server. (deprecated, for backwards compatibility only) - `idp_client_id`: Client ID for communication with the Identity Provider (IdP). - `idp_client_secret`: Secret key for authenticating with the Identity Provider (IdP). - `idp_tenant_id`: Identifier for the specific tenant in the Identity Provider's system. @@ -95,7 +94,6 @@ An example configuration file can be found in `example-config.yaml`. A breakdown - `idp_provider_url`: URL of the Identity Provider service. - `idp_refresh_directory_interval`: How often the directory information should be refreshed from the Identity Provider. - `idp_refresh_directory_timeout`: How long the system should wait for a directory refresh before timing out. -- `metrics_address`: Address and port where the metrics server should listen. - `signing_key`: RSA private key for signing JWT tokens, encoded in base64. - `redirect_base_path`: Base path for redirection URLs. By default `/.veriflow` - `jwks_path`: Location of the JSON Web Key Set (JWKS) that can be called to get the public keys of the signing key. diff --git a/example-config.yaml b/example-config.yaml index e3d09e2..98c5fad 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -5,8 +5,8 @@ ui: logo_image_src: https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/1200px-Google_2015_logo.svg.png error_page_show_error_code: false -auth_listen_port: 3000 data_listen_port: 2080 +metrics_listen_port: 9090 service_url: https://veriflow.codemo.re cookie_secret: "ThisIsAFakeCookieSecret" cookie_settings: @@ -23,7 +23,6 @@ idp_provider_user_id_claim: oid idp_provider_url: https://login.microsoftonline.com/00000000-1111-2222-3333-444444444444/v2.0 idp_refresh_directory_interval: 10m idp_refresh_directory_timeout: 5m -metrics_address: 0.0.0.0:9090 signing_key: "BASE64_ENCODED_RSA_PRIVATE_KEY" redirect_base_path: /.veriflow jwks_path: /.well-known/veriflow/jwks.json diff --git a/lib/http.js b/lib/http.js index 3825edb..8143f02 100644 --- a/lib/http.js +++ b/lib/http.js @@ -6,7 +6,7 @@ import ssoController from './sso.js'; import redisHelper from "../util/redis.js" import log from '../util/logging.js' -import { getConfig, getRedirectBasepath } from '../util/config.js'; +import { getConfig, getRedirectBasepath, getAuthListenPort } from '../util/config.js'; import { pem2jwk } from 'pem-jwk'; import crypto from 'crypto'; import errorpages from '../util/errorpage.js' @@ -14,13 +14,10 @@ import errorpages from '../util/errorpage.js' var trusted_ranges = ["loopback"].concat(getConfig().trusted_ranges || []) +log.info({ message: `Setting trusted proxies to ${trusted_ranges}` }) +app.set('trust proxy', trusted_ranges) -if (trusted_ranges) { - log.info({ message: `Setting trusted proxies to ${trusted_ranges}` }) - app.set('trust proxy', trusted_ranges) -} - - +var defaultCookieOptions = { maxAge: 3600000 } // Initialize sesssion storage. app.use( @@ -30,7 +27,10 @@ app.use( resave: false, saveUninitialized: false, secret: getConfig().cookie_secret, - cookie: getConfig().cookie_settings || {} + cookie: { + ...defaultCookieOptions, + ...getConfig().cookie_settings + } }) ) @@ -84,4 +84,4 @@ app.get(getConfig().jwks_path, (req, res) => { }); }); -app.listen(getConfig().auth_listen_port, () => log.debug("Veriflow HTTP server running")); +app.listen(getAuthListenPort(), 'localhost', () => log.debug("Veriflow HTTP server running")); diff --git a/lib/sso.js b/lib/sso.js index 8d41930..4137d60 100644 --- a/lib/sso.js +++ b/lib/sso.js @@ -144,7 +144,15 @@ async function setSessionCookie(req, res) { // This sets the cookie for the accessed domain to expire at the same time as the "main" veriflow cookie, to // prevent a user from being deauthenticated from Veriflow, but still authenticated on the subdomains. - req.session.cookie.expires = decoded.cookieExpires + var expireDate = false + try { + var date = new Date(decoded.cookieExpires) + expireDate = date + } catch (error) { + log.error({ message: "Unable to get cookie expiry from decoded date", context: decoded}) + } + + req.session.cookie.expires = expireDate var redirectProtocol = decoded.protocol var redirectHost = decoded.host diff --git a/test/e2e/configs/dex.yaml b/test/e2e/configs/dex.yaml index 0419b46..887a9b2 100644 --- a/test/e2e/configs/dex.yaml +++ b/test/e2e/configs/dex.yaml @@ -1,4 +1,4 @@ -issuer: http://127.0.0.1:5556/dex +issuer: http://dex.localtest.me:5556/dex storage: type: sqlite3 config: @@ -18,6 +18,7 @@ staticClients: - id: 0c4860a4-ae2b-4f49-97d7-b581252a7166 redirectURIs: - 'http://localhost:2080/.veriflow/callback' + - 'http://veriflow.localtest.me/.veriflow/callback' name: 'Veriflow' secret: supersecret diff --git a/test/e2e/configs/veriflow.yaml b/test/e2e/configs/veriflow.yaml index 854dac9..fdbe2e0 100644 --- a/test/e2e/configs/veriflow.yaml +++ b/test/e2e/configs/veriflow.yaml @@ -1,7 +1,7 @@ --- -auth_listen_port: 3000 -data_listen_port: 2080 -service_url: http://localhost:2080 +data_listen_port: 80 +metrics_listen_port: 9090 +service_url: http://veriflow.localtest.me cookie_secret: "fakeCookieSecret" redis_connection_string: redis://127.0.0.1:6379 idp_client_id: 0c4860a4-ae2b-4f49-97d7-b581252a7166 @@ -10,14 +10,12 @@ idp_provider: localfile idp_provider_localfile_location: output.json idp_provider_scope: openid email profile idp_provider_user_id_claim: email -idp_provider_url: http://localhost:5556/dex +idp_provider_url: http://dex.localtest.me:5556/dex idp_refresh_directory_interval: 10m idp_refresh_directory_timeout: 5m refresh_idp_at_start: true -metrics_address: 0.0.0.0:9090 # BELOW KEY IS ONLY FOR TESTING AND NOT A REAL KEY signing_key: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBazdPT1kyZzk4QnVRWFFGN0d6VXQ3V3pQc1hsQXBXYWg5blozMDZwZzJUV0NXYm95CmIwekJEVWpmeWZHWDNEUnl1NTdZSFgwL2pwWUdhcGt6YW41SkxWSkl2MmpsQnZkWGZOREFvRkFWVURnTFJYa3QKN3FlMnRNazFGMEFoQnBpbWdQSUlMbU9HRVJrSzZ4cmFmRHorTXRyV2NOVnNqdmJUZ1ZuSC85K2lBSGZXSTQ3Zwp4NWd1MmJuR1U2N3cyN3BvZGo1ZWsyUGdBR0cwb2M0dUJiY3V4cGFOSy9xdERnZU1samtyRWc0S0NpYmlFMUFuCm5aUUc2aGkrNkJuQnNWQkRWTDV5L0xkOUhrS0FxNDB2MHQ4VThKZ0xqNElmcUJ4c2Z0WllyL2Y4UDhoR2Z1N0UKek13ZS91MnU2NXZCRVBlRnNYQk14UmZqQ041enVlVWI2L1RRaXdJREFRQUJBb0lCQVFDUlZ1bXhKZjEwelJyVQpla1dTYzFUN1FjeHFQZitBQXFzelpFWHJVY2UxVlhNc0tnM0ErYzBwN21EUVRkeDZRbDM0QTRsMEV6QThkYUpnCnVOb2dXNTVVYTVqTVNVSzlCUnpnNUdYNEduV3VsMGQ0R0pNN09XdVBJRU1PMnZya2k4ZWtNUVlkNTY4Z0dmMWwKZGVveXdLMytpdHJpOHhDODZXTWM4S1RlUTBnZG5rKzBOM0h0cWx5N005TG9laElpVHMyMGRBSGcwT1NGaXdyYwpiL2Y1T0MybFRhbmdJb1FpbzFvTEwwRUM2dk4wdnpUUkxnNW9hMXhUMTlDN21FcFprL1ZnUjBuQlpRcU4vNDlICklvTGpIWWNFbXVsZDh1UU14dUZ1RzVLN0lpQlBVKys1QWhvbmUwTDlvQnB2NFlYeVM2OXE0amU0ZjNFSllPa2QKS2VndmVJa3BBb0dCQU1RVUhheDBhOHVaTWNaZnhvaG9CMjVuaE53Nng1VEZtVGRQeC9DamxYWllBYWcrbUFGYwpTWnF4SEJHcVJTTFRQMVkreFp6VExlS0VJNnlLSXJwbHozMXZPMEdsU05Xb3UybGkvUFRBQlBUVnl3eEcvWVpHCmZUL3BhZXJXYzJuYkxnUngxMkl5TzhsQXIwUVFJU3ZHdlF0cUdHUlpQY2YxSHJMQU81dkgzK2U5QW9HQkFNRFcKdXgxMmo5U1BZT3NBN2E1b2JmSzZmazNnTVFjeEZVRGVUaU1GSHl3bFdoWThQOUhqelRHVUNEQ3gzT2lwSHdNZApBUWZOTXRMRDR4WThZS05Ha2t1YklFemFRL0hWckRIQklZdGNsMHFobUZzRDAyYXJqWnlNV2xQN0FySEZlenNhCnZGMHR5eEIzQlFjVWE0REpqcG1jN1V2S2l6Zm4xVEl0a2NXL1lsbm5Bb0dCQUpndEFJYXFhRXJBWDNnVk52RUEKdzl1MHJkRjZNUkZPZGtZT1BoK041ZDdPR0tNcHlURXRIZGJYNCsvMTFPaGRTUWUzZWdqbmdQSVBHZHk3N0kzNwpuQmcrcnArWkZyanoxbGZKUW9iMVRDTjBsYnkyaithWmFIV2t3dFpHajVZMVREYVkzODlQSzBWYlZXc2VsWS96CkV4Nzd2V2lNTmoydENLRTBQazc5eGRHRkFvR0JBSUVNc3JmcTZpSWp1WVpMWHNSQzJxRi9zSnJKRjhacVVJRFMKeEpPbkQ4OXBSN3B0bzRBQTVRYnl1L0JxZHgyMFlDNmpNRmRhT1ZMWENKZU8zRlVvR3l0QnF3SURaMGpsNTVCOApZTWgwdEVLYmxld0N5V3lDRGdqZjNHc3JKZ2gxMGh3aHJrRGxMbW5jWEo3NlNWOHNnNlBGWXdBL2taOWVKRXlxCk5rMlI0RzJ0QW9HQWZVL092bHdCSWlBN0p3Wjc4c2x5MVFPMkkrbWdybHo5OWRERlNRcTAwQTVUZjd2dGFEYncKNzQwRXBGc0lNRm5BNlZRQUg5YVV1RnVxSVV4K1RQazMyQXdJTy9SNVpLR2dNelNqcndDRVNBeXMyR0JJdWx1TwpFcktHNUp1NmFteWUyWk90eEl5emdWV3Zmc0hERG5MNDRuTmNhYkNDbnRQR1dJb3QyeXhnZllvPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" # ONLY FOR TESTING -xff_num_trusted_hops: 2 redirect_base_path: /.veriflow jwks_path: /.well-known/pomerium/jwks.json kid_override: "0" diff --git a/test/e2e/tests/basic_test.js b/test/e2e/tests/basic_test.js index f27e405..0f15795 100644 --- a/test/e2e/tests/basic_test.js +++ b/test/e2e/tests/basic_test.js @@ -1,39 +1,39 @@ Feature('Login').retry(3); Scenario('Basic login test', async ({ I }) => { - I.amOnPage('http://test-basic-login.localtest.me:2080/get'); + I.amOnPage('http://test-basic-login.localtest.me/get'); I.login(); I.see("x-veriflow-user-id") }); Scenario('HTTPS Upstream Test', async ({ I }) => { - I.amOnPage('http://test-https-upstream.localtest.me:2080/get'); + I.amOnPage('http://test-https-upstream.localtest.me/get'); I.login(); I.see("x-veriflow-user-id") }); Scenario('Removing headers', async ({ I }) => { - I.amOnPage('http://test-removing-headers.localtest.me:2080/get'); + I.amOnPage('http://test-removing-headers.localtest.me/get'); I.login(); I.dontSee("x-veriflow-user-id") }); Scenario('Adding headers', async ({ I }) => { - I.amOnPage('http://test-adding-headers.localtest.me:2080/get'); + I.amOnPage('http://test-adding-headers.localtest.me/get'); I.login(); I.see("x-pomerium-claim-email") I.see("x-test-header") }); Scenario('Testing mTLS', async ({ I }) => { - I.amOnPage('http://test-mtls-auth.localtest.me:2080/'); + I.amOnPage('http://test-mtls-auth.localtest.me/'); I.login(); I.see("Veriflow-Test-Cert") I.see("Veriflow-Test-CA") }); Scenario('Testing Header Mapping', async ({ I }) => { - I.amOnPage('http://test-header-mapping.localtest.me:2080/'); + I.amOnPage('http://test-header-mapping.localtest.me/'); I.login(); I.see("ThisIsATestHeaderFromTheHeaderMapping") I.see("TestHeaderFromGroup") @@ -41,7 +41,7 @@ Scenario('Testing Header Mapping', async ({ I }) => { }); Scenario('Testing Header Mapping Inline', async ({ I }) => { - I.amOnPage('http://test-header-mapping-inline.localtest.me:2080/'); + I.amOnPage('http://test-header-mapping-inline.localtest.me/'); I.login(); I.see("ThisIsATestHeaderFromTheHeaderMapping") I.see("TestHeaderFromGroup") @@ -52,37 +52,37 @@ Scenario('Testing Token Auth', async ({ I }) => { I.setPuppeteerRequestHeaders({ 'Authorization': 'Bearer ThisIsATestToken', }); - I.amOnPage('http://test-token-auth.localtest.me:2080/'); + I.amOnPage('http://test-token-auth.localtest.me/'); I.see("x-veriflow-user-id") }); Scenario('Testing Unauthorized Flow', async ({ I }) => { - I.amOnPage('http://test-unauthorized-login.localtest.me:2080/'); + I.amOnPage('http://test-unauthorized-login.localtest.me/'); I.login() I.see("ERR_NOT_AUTHORIZED") }); Scenario('Advanced matchers test', async ({ I }) => { - I.amOnPage('http://test-advanced-matchers.localtest.me:2080/get'); + I.amOnPage('http://test-advanced-matchers.localtest.me/get'); I.login(); I.see("x-veriflow-user-id") - I.amOnPage('http://test-advanced-matchers.localtest.me:2080/should404'); + I.amOnPage('http://test-advanced-matchers.localtest.me/should404'); I.see("ERR_ROUTE_NOT_FOUND") }); Scenario('Dynamic Upstreams Test', async ({ I }) => { - I.amOnPage('http://test-dynamic-upstreams.localtest.me:2080/get'); + I.amOnPage('http://test-dynamic-upstreams.localtest.me/get'); I.login(); I.see("x-veriflow-user-id") }); Scenario('Dynamic Upstreams Test SRV', async ({ I }) => { - I.amOnPage('http://test-dynamic-upstreams-srv.localtest.me:2080/get'); + I.amOnPage('http://test-dynamic-upstreams-srv.localtest.me/get'); I.login(); I.see("x-veriflow-user-id") }); Scenario('Unauthenticated Access test', async ({ I }) => { - I.amOnPage('http://test-unauthenticated-access.localtest.me:2080/get'); + I.amOnPage('http://test-unauthenticated-access.localtest.me/get'); I.see("x-public-access") }); \ No newline at end of file diff --git a/util/caddyModels.js b/util/caddyModels.js index f8c1966..40b7b49 100644 --- a/util/caddyModels.js +++ b/util/caddyModels.js @@ -1,4 +1,4 @@ -import { getConfig } from "./config.js" +import { getConfig, getAuthListenPort } from "./config.js" import log from './logging.js'; import { writeFile, stat } from "fs/promises"; import axios from 'axios'; @@ -173,7 +173,7 @@ async function saturateRoute(route, routeId) { handler: "reverse_proxy", upstreams: [ { - dial: "localhost:" + config.auth_listen_port + dial: "localhost:" + getAuthListenPort() } ] } @@ -243,7 +243,7 @@ async function saturateRoute(route, routeId) { }, upstreams: [ { - dial: "localhost:" + config.auth_listen_port + dial: "localhost:" + getAuthListenPort() } ] } @@ -360,7 +360,7 @@ async function generateCaddyConfig() { "handler": "reverse_proxy", "upstreams": [ { - "dial": "localhost:" + config.auth_listen_port + "dial": "localhost:" + getAuthListenPort() } ] } diff --git a/util/config.js b/util/config.js index 77a6c31..300965d 100644 --- a/util/config.js +++ b/util/config.js @@ -5,6 +5,8 @@ import log from './logging.js'; import reloadCaddy from './caddyModels.js'; import chokidar from 'chokidar'; +let foundPort = false + let configFileLocation = process.env.CONFIG_FILE || "config.yaml" let currentConfig = yaml.load(fsSync.readFileSync(configFileLocation, 'utf8')) @@ -55,9 +57,30 @@ function getRedirectBasepath() { return redirectBasePath } +function getAuthListenPort() { + if (foundPort) { + return foundPort + } + var config = getConfig() + var metricsListenPort = config.metrics_listen_port + var dataListenPort = config.data_listen_port + var authListenPort = 9847 + while (foundPort == false) { + if ((metricsListenPort == authListenPort) || (dataListenPort == authListenPort)) { + log.info({ message: `Port ${authListenPort} is taken, trying another port for the auth service`}) + authListenPort++ + } else { + foundPort = authListenPort + log.info({ message: `Found port ${foundPort} for the auth service to listen on`}) + } + } + return foundPort +} + export { reloadConfig, getConfig, getRouteFromRequest, - getRedirectBasepath + getRedirectBasepath, + getAuthListenPort }; \ No newline at end of file