Skip to content

Commit

Permalink
feat: use secure cookie session backed by connect-pg-simple
Browse files Browse the repository at this point in the history
- set cookie to be secure when https is enabled
- remove the hardcoded symmetric key
- auto-generate a good symmetric key with helm
- auto-generate an ephemeral symmetric key in dev environment
- deploy a session table to the private postgres schema
- use connect-pg-simple as postgres backing store for session
  • Loading branch information
wenzowski committed Jun 19, 2020
1 parent 848183b commit fa1de35
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 8 deletions.
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"@react-pdf/styled-components": "^1.4.0",
"ace-builds": "^1.4.11",
"body-parser": "^1.19.0",
"connect-pg-simple": "^6.1.0",
"cors": "^2.8.5",
"decimal.js-light": "^2.5.0",
"deep-diff": "^1.0.2",
Expand Down
44 changes: 36 additions & 8 deletions app/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ const {postgraphile} = require('postgraphile');
const next = require('next');
const PgManyToManyPlugin = require('@graphile-contrib/pg-many-to-many');

const crypto = require('crypto');
const pg = require('pg');
const port = Number.parseInt(process.env.PORT, 10) || 3004;
const dev = process.env.NODE_ENV !== 'production';
const app = next({dev});
const handle = app.getRequestHandler();
const session = require('express-session');
const PgSession = require('connect-pg-simple')(session);
const bodyParser = require('body-parser');
const Keycloak = require('keycloak-connect');
const cors = require('cors');
Expand Down Expand Up @@ -68,11 +71,26 @@ if (process.env.PGPORT) {
databaseURL += '/';
databaseURL += process.env.PGDATABASE || 'ciip_portal_dev';

// True if the host has been configured to use https
// eslint-disable-next-line unicorn/prefer-starts-ends-with, @typescript-eslint/prefer-string-starts-ends-with
const secure = /^https/.test(process.env.HOST);

// Ensure we properly crypt our cookie session store with a pre-shared key in a secure environment
if (secure && typeof process.env.SESSION_SECRET !== typeof String())
throw new Error('export SESSION_SECRET to encrypt session cookies');
if (secure && process.env.SESSION_SECRET.length < 24)
throw new Error('exported SESSION_SECRET must be at least 24 characters');
if (!process.env.SESSION_SECRET)
console.warn('SESSION_SECRET missing from environment');
const secret = process.env.SESSION_SECRET || crypto.randomBytes(32).toString();

const pgPool = new pg.Pool({connectionString: databaseURL});

// Graphile-worker function
async function worker() {
// Run a worker to execute jobs:
await run({
connectionString: databaseURL,
pgPool,
concurrency: 5,
pollInterval: 1000,
taskDirectory: path.resolve(__dirname, 'tasks')
Expand Down Expand Up @@ -126,20 +144,28 @@ const getRedirectURL = (req) => {
app.prepare().then(() => {
const server = express();

// Enable serving ACME HTTP-01 challenge response written to disk by acme.sh
// https://letsencrypt.org/docs/challenge-types/#http-01-challenge
// https://github.com/acmesh-official/acme.sh
server.use(
'/.well-known',
express.static(path.resolve(__dirname, '../.well-known'))
);
server.use(bodyParser.json());
server.use(cors());

const memoryStore = new session.MemoryStore();
const store = new PgSession({
pool: pgPool,
schemaName: 'ggircs_portal_private',
tableName: 'session'
});
server.use(
session({
secret: 'change me pls for the love of Jibbers Crabst',
secret,
resave: false,
saveUninitialized: true,
store: memoryStore
cookie: {secure},
store
})
);

Expand All @@ -155,7 +181,7 @@ app.prepare().then(() => {
'public-client': true,
'confidential-port': 0
};
const keycloak = new Keycloak({store: memoryStore}, kcConfig);
const keycloak = new Keycloak({store}, kcConfig);

server.use(
keycloak.middleware({
Expand All @@ -164,8 +190,11 @@ app.prepare().then(() => {
})
);

// The graphile config object should be swapped in dev/prod
// https://www.graphile.org/postgraphile/usage-library/#for-development
// https://www.graphile.org/postgraphile/usage-library/#for-production
server.use(
postgraphile(databaseURL, process.env.DATABASE_SCHEMA || 'ggircs_portal', {
postgraphile(pgPool, process.env.DATABASE_SCHEMA || 'ggircs_portal', {
appendPlugins: [PgManyToManyPlugin],
graphiql: true,
classicIds: true,
Expand Down Expand Up @@ -301,8 +330,7 @@ app.prepare().then(() => {
return handle(req, res);
});

// eslint-disable-next-line unicorn/prefer-starts-ends-with, @typescript-eslint/prefer-string-starts-ends-with
if (/^https/.test(process.env.HOST)) {
if (secure) {
const domain = /^https:\/\/(.+?)\/?$/.exec(process.env.HOST)[1];
const key = fs.readFileSync(
`/root/.acme.sh/${domain}/${domain}.key`,
Expand Down
36 changes: 36 additions & 0 deletions app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5815,6 +5815,13 @@ confusing-browser-globals@1.0.9:
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd"
integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==

connect-pg-simple@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/connect-pg-simple/-/connect-pg-simple-6.1.0.tgz#18640d364f8a39ca368b789dc3f36f63872b089d"
integrity sha512-pWRuser61Opj/LtzrkuRkmBcCYY1dvZ7jLu83rR7vIsTzFpmQoe1KcmMalwlN3rCq1VVHssGjY42SCSe2vEizQ==
dependencies:
pg "^7.4.3"

console-browserify@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
Expand Down Expand Up @@ -13777,6 +13784,11 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=

pg-connection-string@0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7"
integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=

pg-connection-string@^2.0.0, pg-connection-string@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.2.2.tgz#fdc2fb2084e655f0b3e6e50c69330f6932df452e"
Expand All @@ -13787,6 +13799,16 @@ pg-int8@1.0.1:
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==

pg-packet-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz#e45c3ae678b901a2873af1e17b92d787962ef914"
integrity sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg==

pg-pool@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.10.tgz#842ee23b04e86824ce9d786430f8365082d81c4a"
integrity sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg==

pg-pool@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.2.0.tgz#edde90f21556f83d16c45f1efd696af4afec6f2f"
Expand Down Expand Up @@ -13831,6 +13853,20 @@ pg-types@^2.1.0:
pgpass "1.x"
semver "4.3.2"

pg@^7.4.3:
version "7.18.2"
resolved "https://registry.yarnpkg.com/pg/-/pg-7.18.2.tgz#4e219f05a00aff4db6aab1ba02f28ffa4513b0bb"
integrity sha512-Mvt0dGYMwvEADNKy5PMQGlzPudKcKKzJds/VbOeZJpb6f/pI3mmoXX0JksPgI3l3JPP/2Apq7F36O63J7mgveA==
dependencies:
buffer-writer "2.0.0"
packet-reader "1.0.0"
pg-connection-string "0.1.3"
pg-packet-stream "^1.1.0"
pg-pool "^2.0.10"
pg-types "^2.1.0"
pgpass "1.x"
semver "4.3.2"

pgpass@1.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306"
Expand Down
5 changes: 5 additions & 0 deletions helm/cas-ciip-portal/templates/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ spec:
imagePullPolicy: {{ .Values.image.pullPolicy }}
image: {{ .Values.image.app.repository }}:{{ .Values.image.app.tag }}
env:
- name: SESSION_SECRET
valueFrom:
secretKeyRef:
key: session-secret
name: {{ template "cas-ciip-portal.fullname" . }}
- name: PGUSER
valueFrom:
secretKeyRef:
Expand Down
1 change: 1 addition & 0 deletions helm/cas-ciip-portal/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ metadata:
"helm.sh/hook-delete-policy": "before-hook-creation"
type: Opaque
data:
session-secret: {{ randAlphaNum 32 | b64enc | quote }}
database-name: {{ .Values.persistence.db | b64enc | quote }}
database-user: {{ .Values.persistence.dbOwner | b64enc | quote }}
database-password: {{ randAlphaNum 32 | b64enc | quote }}
Expand Down
26 changes: 26 additions & 0 deletions schema/deploy/tables/session.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- Deploy ggircs-portal:tables/session to pg
-- requires: schema_ggircs_portal_private

begin;

create table ggircs_portal_private.session (
sid varchar(4093) not null collate "default",
sess json not null,
expire timestamp(6) not null
)
with (oids=false);

alter table ggircs_portal_private.session
add constraint ggircs_portal_private_session_pkey primary key (sid) not deferrable initially immediate;

create index ggircs_portal_private_idx_session_expire
on ggircs_portal_private.session(expire);

grant all on ggircs_portal_private.session to public;

comment on table ggircs_portal_private.session is 'The backing store for connect-pg-simple to store express session data';
comment on column ggircs_portal_private.session.sid is 'The value of the symmetric key encrypted connect.sid cookie';
comment on column ggircs_portal_private.session.sess is 'The express session middleware object picked as json containing the jwt';
comment on column ggircs_portal_private.session.expire is 'The timestamp after which this session object will be garbage collected';

commit;
7 changes: 7 additions & 0 deletions schema/revert/tables/session.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Revert ggircs-portal:tables/session from pg

begin;

drop table ggircs_portal_private.session;

commit;
2 changes: 2 additions & 0 deletions schema/sqitch.plan
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,5 @@ computed_columns/product_linked_product [tables/linked_product types/linked_prod
@v1.0.0-rc.5 2020-06-17T18:45:39Z Matthieu Foucault <matthieu@button.is> # release v1.0.0-rc.5
@v1.0.0-rc.6 2020-06-17T23:02:56Z Matthieu Foucault <matthieu@button.is> # release v1.0.0-rc.6
@v1.0.0-rc.7 2020-06-18T18:52:29Z Matthieu Foucault <matthieu@button.is> # release v1.0.0-rc.7

tables/session [schema_ggircs_portal_private] 2020-06-19T01:45:52Z Alec Wenzowski <alec@button.is> # session support for connect-pg-simple
7 changes: 7 additions & 0 deletions schema/verify/tables/session.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Verify ggircs-portal:tables/session on pg

begin;

select pg_catalog.has_table_privilege('ggircs_portal_private.session', 'select');

rollback;

0 comments on commit fa1de35

Please sign in to comment.