Skip to content

Commit

Permalink
Merge pull request #26 from gristlabs/jordigh/revert-changes
Browse files Browse the repository at this point in the history
  • Loading branch information
georgegevoian authored Sep 6, 2024
2 parents 76500e5 + b27557a commit df2fa08
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 28 deletions.
35 changes: 29 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,33 @@
# Grist doesn't have a built-in login system, which can be
# a stumbling block for beginners or people just wanting to
# try it out.
# Includes bundled traefik and dex.
# Includes bundled traefik, traefik-forward-auth, and dex.

ARG BASE=gristlabs/grist:latest

# Gather main dependencies.
FROM dexidp/dex:v2.33.1 AS dex
FROM traefik:2.8 AS traefik
FROM traefik/whoami AS whoami
FROM dexidp/dex:v2.33.1 as dex
FROM traefik:2.8 as traefik
FROM traefik/whoami as whoami

# recent public traefik-forward-auth image doesn't support arm,
# so build it from scratch.
FROM golang:1.13-alpine as fwd
RUN mkdir -p /go/src/github.com/thomseddon/traefik-forward-auth
WORKDIR /go/src/github.com/thomseddon/traefik-forward-auth
RUN apk add --no-cache git
RUN mkdir -p /go/src/github.com/thomseddon/
RUN cd /go/src/github.com/thomseddon/ && \
git clone https://github.com/thomseddon/traefik-forward-auth.git && \
cd traefik-forward-auth && \
git checkout c4317b7503fb0528d002eb1e5ee43c4a37f055d0
ARG TARGETOS TARGETARCH
RUN echo "Compiling for [$TARGETOS $TARGETARCH] (will be blank if not using BuildKit)"
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH GO111MODULE=on go build -a -installsuffix nocgo \
-o /traefik-forward-auth github.com/thomseddon/traefik-forward-auth/cmd

# Extend Grist image.
FROM $BASE AS merge
FROM $BASE as merge

# Enable sandboxing by default. It is generally important when sharing with
# others. You may override it, e.g. "unsandboxed" uses no sandboxing but is
Expand All @@ -27,6 +43,9 @@ RUN \
apt-get install -y --no-install-recommends ca-certificates tzdata && \
rm -rf /var/lib/apt/lists/*

# Copy in traefik-forward-auth program.
COPY --from=fwd /traefik-forward-auth /usr/local/bin

# Copy in traeefik program.
COPY --from=traefik /usr/local/bin/traefik /usr/local/bin/traefik

Expand All @@ -46,12 +65,16 @@ COPY dex.yaml /settings/dex.yaml
COPY traefik.yaml /settings/traefik.yaml
COPY run.js /grist/run.js

# Make traefik-forward-auth trust self-signed certificates internally, if user
# chooses to use one.
RUN ln -s /custom/grist.crt /etc/ssl/certs/grist.pem

# Squashing this way loses environment variables set in base image
# so we need to revert it for now.
# # One last layer, to squash everything.
# FROM scratch
# COPY --from=merge / /

CMD ["/grist/run.js"]
CMD /grist/run.js

EXPOSE 80 443
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PORT = 9999
PORT = 8484
TEAM = cool-beans

# Possible bases: gristlabs/grist, or gristlabs/grist-ee
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ It bundles:
Microsoft, etc, and also (somewhat reluctantly) supports
hard-coded user/passwords that can be handy for a quick
fuss-free test.
* An authentication middleware, [traefik-forward-auth](https://github.com/thomseddon/traefik-forward-auth) to
connect Grist and Dex via Traefik.

Here's the minimal configuration you need to provide.
* `EMAIL`: an email address, used for Let's Encrypt and for
Expand Down
6 changes: 3 additions & 3 deletions dex.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ frontend:
logoURL: '{{ getenv "APP_HOME_URL" }}/v/unknown/ui-icons/Logo/GristLogo.svg'

staticClients:
- id: '{{ getenv "GRIST_OIDC_IDP_CLIENT_ID" }}'
- id: '{{ getenv "PROVIDERS_OIDC_CLIENT_ID" }}'
redirectURIs:
- '{{ getenv "APP_HOME_URL" }}/oauth2/callback'
- '{{ getenv "APP_HOME_URL" }}/_oauth'
name: 'Grist'
secret: '{{ getenv "GRIST_OIDC_IDP_CLIENT_SECRET" }}'
secret: '{{ getenv "PROVIDERS_OIDC_CLIENT_SECRET" }}'


oauth2:
Expand Down
127 changes: 109 additions & 18 deletions run.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
const fs = require('fs');
const child_process = require('child_process');
const colors = require('colors/safe');
const commander = require('commander');
const fetch = require('node-fetch');
const https = require('https');
const path = require('path');

function consoleLogger(level, color) {
Expand All @@ -17,20 +20,38 @@ const log = {


async function main() {
const {program} = commander;
program.option('-p, --part <part>');
program.parse();
const options = program.opts();
const part = options.part || 'all';

prepareDirectories();
prepareMainSettings();
prepareNetworkSettings();
prepareCertificateSettings();

startGrist();
startTraefik();
startWho();
startDex();

if (part === 'grist' || part === 'all') {
startGrist();
}
if (part === 'traefik' || part === 'all') {
startTraefik();
}
if (part === 'who' || part === 'all') {
startWho();
}
if (part === 'dex' || part === 'all') {
startDex();
}
if (part === 'tfa' || part === 'all') {
await waitForDex();
startTfa();
}
await sleep(1000);
log.info('I think everything has started up now');
const ports = process.env.HTTPS ? '80/443' : '80';
log.info(`Listening internally on ${ports}, externally at ${process.env.URL}`);
if (part === 'all') {
const ports = process.env.HTTPS ? '80/443' : '80';
log.info(`Listening internally on ${ports}, externally at ${process.env.URL}`);
}
}

main().catch(e => log.error(e));
Expand Down Expand Up @@ -77,11 +98,14 @@ function startTraefik() {
flags.push("--entrypoints.web.http.redirections.entrypoint.scheme=https")
flags.push("--entrypoints.web.http.redirections.entrypoint.to=websecure")
}
let TFA_TRUST_FORWARD_HEADER = 'false';
if (process.env.TRUSTED_PROXY_IPS) {
flags.push(`--entryPoints.web.forwardedHeaders.trustedIPs=${process.env.TRUSTED_PROXY_IPS}`)
TFA_TRUST_FORWARD_HEADER = 'true';
}
log.info("Calling traefik", flags);
essentialProcess("traefik", child_process.spawn('traefik', flags, {
env: {...process.env, TFA_TRUST_FORWARD_HEADER},
stdio: 'inherit',
detached: true,
}));
Expand All @@ -107,6 +131,17 @@ function startDex() {
}));
}

function startTfa() {
log.info('Starting traefik-forward-auth');
essentialProcess("traefik-forward-auth", child_process.spawn('traefik-forward-auth', [
`--port=${process.env.TFA_PORT}`
], {
env: process.env,
stdio: 'inherit',
detached: true,
}));
}

function startWho() {
child_process.spawn('whoami', {
env: {
Expand Down Expand Up @@ -150,25 +185,49 @@ function prepareMainSettings() {
if (!process.env.GRIST_SESSION_SECRET) {
process.env.GRIST_SESSION_SECRET = invent('GRIST_SESSION_SECRET');
}

// When not using https either manually or via automation, the user
// presumably will tolerate cookies sent without https. See:
// https://community.getgrist.com/t/solved-local-use-without-https/2852/11
if (!process.env.HTTPS && !process.env.INSECURE_COOKIE) {
// see https://github.com/thomseddon/traefik-forward-auth for
// documentation. This environment variable will be set when
// the traefik-forward-auth process is started (and others too,
// but won't have an impact on them).
process.env.INSECURE_COOKIE = 'true';
}
}

function prepareNetworkSettings() {
const url = new URL(process.env.URL);
process.env.APP_HOST = url.hostname || 'localhost';
process.env.DEX_PORT = url.port || '9999';
// const extPort = parseInt(url.port || '9999', 10);
const extPort = url.port || '9999';
process.env.EXT_PORT = extPort;

// traefik-forward-auth will try to talk directly to dex, so it is
// important that URL works internally, withing the container. But
// if URL contains localhost, it really won't. We can finess that
// by tying DEX_PORT to EXT_PORT in that case. As long as it isn't
// 80 or 443, since traefik is listening there...

process.env.DEX_PORT = '9999';
if (process.env.APP_HOST === 'localhost' && extPort !== '80' && extPort !== '443') {
process.env.DEX_PORT = process.env.EXT_PORT;
}

// Keep other ports out of the way of Dex port.
const alt = String(process.env.DEX_PORT).charAt(0) === '1' ? '2' : '1';
process.env.GRIST_PORT = `${alt}7100`;
process.env.WHOAMI_PORT = `${alt}7101`;

// Setup OIDC
process.env.GRIST_OIDC_SP_HOST = process.env.APP_HOME_URL;
process.env.GRIST_OIDC_IDP_ISSUER = `${process.env.APP_HOME_URL}/dex`;
process.env.GRIST_OIDC_IDP_CLIENT_ID = invent('GRIST_OIDC_IDP_CLIENT_ID');
process.env.GRIST_OIDC_IDP_CLIENT_SECRET = invent('GRIST_OIDC_IDP_CLIENT_SECRET');
process.env.GRIST_OIDC_IDP_END_SESSION_ENDPOINT = `${process.env.APP_HOME_URL}/signed-out`;

process.env.TFA_PORT = `${alt}7101`;
process.env.WHOAMI_PORT = `${alt}7102`;

setBrittleEnv('DEFAULT_PROVIDER', 'oidc');
process.env.PROVIDERS_OIDC_CLIENT_ID = invent('PROVIDERS_OIDC_CLIENT_ID');
process.env.PROVIDERS_OIDC_CLIENT_SECRET = invent('PROVIDERS_OIDC_CLIENT_SECRET');
process.env.PROVIDERS_OIDC_ISSUER_URL = `${process.env.APP_HOME_URL}/dex`;
process.env.SECRET = invent('TFA_SECRET');
process.env.LOGOUT_REDIRECT = `${process.env.APP_HOME_URL}/signed-out`;
}

function setSynonym(name1, name2) {
Expand Down Expand Up @@ -253,6 +312,38 @@ function addDexUsers() {
return txt.join('\n') + '\n';
}

async function waitForDex() {
const fetchOptions = process.env.HTTPS ? {
agent: new https.Agent({
// If we are responsible for certs, wait for them to be
// set up and valid - don't accept default self-signed
// traefik certs. Otherwise traefik-forward-auth will
// fail immediately if it sees a self-signed cert, without
// giving letsencrypt time to make one for us.
//
// Otherwise, don't fret, the responsibility for certs
// being in place before the rest of grist-omnibus starts
// lies elsewhere. We only care if dex is up and running.
rejectUnauthorized: (process.env.HTTPS === 'auto'),
})
} : {};
let delay = 0.1;
while (true) {
const url = process.env.PROVIDERS_OIDC_ISSUER_URL + '/.well-known/openid-configuration';
log.info(`Checking dex... at ${url}`);
try {
const result = await fetch(url, fetchOptions);
log.debug(` got: ${result.status}`);
if (result.status === 200) { break; }
} catch (e) {
log.debug(` not ready: ${e}`);
}
await sleep(1000 * delay);
delay = Math.min(5.0, delay * 1.2);
}
log.info("Happy with dex");
}

function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
Expand Down
13 changes: 13 additions & 0 deletions traefik.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ http:
loadBalancer:
servers:
- url: 'http://127.0.0.1:{{ env "DEX_PORT" }}'
tfa:
loadBalancer:
servers:
- url: 'http://127.0.0.1:{{ env "TFA_PORT" }}'
whoami:
loadBalancer:
servers:
- url: 'http://127.0.0.1:{{ env "WHOAMI_PORT" }}'

middlewares:
tfa:
forwardauth:
address: 'http://127.0.0.1:{{ env "TFA_PORT" }}'
authResponseHeaders: [ '{{ env "GRIST_FORWARD_AUTH_HEADER" }}' ]
trustForwardHeader: '{{ env "TFA_TRUST_FORWARD_HEADER" }}'
no-fwd:
headers:
customRequestHeaders:
Expand All @@ -23,6 +32,8 @@ http:
route-grist-login:
rule: "PathPrefix(`/auth/login`) || PathPrefix(`/_oauth`)"
service: grist
middlewares:
- tfa
entryPoints:
- web

Expand Down Expand Up @@ -52,6 +63,8 @@ http:
https-route-grist-login:
rule: "Host(`{{ env "APP_HOST" }}`) && (PathPrefix(`/auth/login`) || PathPrefix(`/_oauth`))"
service: grist
middlewares:
- tfa
entryPoints:
- websecure
tls: {{ env "TLS" }}
Expand Down

0 comments on commit df2fa08

Please sign in to comment.