Files
skooner/server/index.js
2022-02-22 13:34:41 -06:00

164 lines
5.0 KiB
JavaScript

const cors = require('cors');
const express = require('express');
const http = require('http');
const https = require('https');
const k8s = require('@kubernetes/client-node');
const {createProxyMiddleware} = require('http-proxy-middleware');
const toString = require('stream-to-string');
const {Issuer} = require('openid-client');
const NODE_ENV = process.env.NODE_ENV;
const DEBUG_VERBOSE = !!process.env.DEBUG_VERBOSE;
const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID;
const OIDC_SECRET = process.env.OIDC_SECRET;
const OIDC_URL = process.env.OIDC_URL;
const OIDC_SCOPES = process.env.OIDC_SCOPES || 'openid email';
const OIDC_METADATA = JSON.parse(process.env.OIDC_METADATA || '{}');
const clientMetadata = Object.assign({client_id: OIDC_CLIENT_ID, client_secret: OIDC_SECRET}, OIDC_METADATA);
console.log('OIDC_URL: ', OIDC_URL || 'None');
process.on('uncaughtException', err => console.error('Uncaught exception', err));
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const opts = {};
kc.applyToRequest(opts);
const target = kc.getCurrentCluster().server;
console.log('API URL: ', target);
const agent = new https.Agent({ca: opts.ca});
const proxySettings = {
target,
agent,
ws: true,
secure: false,
changeOrigin: true,
logLevel: 'debug',
onError,
};
if (DEBUG_VERBOSE) {
proxySettings.onProxyRes = onProxyRes;
}
const app = express();
app.disable('x-powered-by'); // for security reasons, best not to tell attackers too much about our backend
app.use(logging);
if (NODE_ENV !== 'production') app.use(cors());
app.use('/', preAuth, express.static('public'));
app.get('/oidc', getOidc);
app.post('/oidc', postOidc);
app.use('/*', createProxyMiddleware(proxySettings));
app.use(handleErrors);
const port = process.env.SERVER_PORT || 4654;
http.createServer(app).listen(port);
console.log(`Server started. Listening on port ${port}`);
function preAuth(req, res, next) {
const auth = req.header('Authorization');
// If the request already contains an authorization header, pass it through to the client (as a cookie)
if (auth && req.method === 'GET' && req.path === '/') {
const value = auth.replace('Bearer ', '');
res.cookie('Authorization', value, {maxAge: 60, httpOnly: false});
console.log('Authorization header found. Passing through to client.');
}
next();
}
function logging(req, res, next) {
res.once('finish', () => console.log(new Date(), req.method, req.url, res.statusCode));
next();
}
async function getOidc(req, res, next) {
try {
const authEndpoint = await getOidcEndpoint();
res.json({authEndpoint});
} catch (err) {
next(err);
}
}
async function postOidc(req, res, next) {
try {
const body = await toString(req);
const {code, redirectUri} = JSON.parse(body);
const token = await oidcAuthenticate(code, redirectUri);
res.json({token});
} catch (err) {
next(err);
}
}
function onError(err, req, res) {
console.log('Error in proxied request', err, req.method, req.url);
}
const SENSITIVE_HEADER_KEYS = ['authorization'];
function scrubHeaders(headers) {
const res = Object.assign({}, headers);
SENSITIVE_HEADER_KEYS.forEach(function(key) {
if (res.hasOwnProperty(key)) {
delete res[key];
}
});
return res;
}
function onProxyRes(proxyRes, req, res) {
const reqHeaders = scrubHeaders(req.headers);
console.log('VERBOSE REQUEST', req.method, req.protocol, req.hostname, req.url, reqHeaders);
const proxyResHeaders = scrubHeaders(proxyRes.headers);
console.log('VERBOSE RESPONSE', proxyRes.statusCode, proxyResHeaders);
}
function handleErrors(err, req, res, next) {
console.error('An error occurred during the request', err, req.method, req.url);
res.status(err.httpStatusCode || 500);
res.send('Server error');
next();
}
async function getOidcEndpoint() {
if (!OIDC_URL) return;
const provider = await getOidcProvider();
return provider.authorizationUrl({scope: OIDC_SCOPES});
}
async function oidcAuthenticate(code, redirectUri) {
const provider = await getOidcProvider();
const tokenSet = await provider.oauthCallback(redirectUri, {code}, {});
return tokenSet.id_token;
}
async function getOidcProvider() {
const issuer = await Issuer.discover(OIDC_URL);
return new issuer.Client(clientMetadata);
}
logClusterInfo();
async function logClusterInfo() {
try {
const versionClient = kc.makeApiClient(k8s.VersionApi);
const versionResponse = await versionClient.getCode();
const versionJson = JSON.stringify(versionResponse.body, null, 4);
console.log('Version Info: ', versionJson);
const apisClient = kc.makeApiClient(k8s.ApisApi);
const apisResponse = await apisClient.getAPIVersions();
const apis = apisResponse.body.groups.map(x => x.preferredVersion.groupVersion).sort();
const apisJson = JSON.stringify(apis, null, 4);
console.log('Available APIs: ', apisJson);
} catch (err) {
console.error('Error getting cluster info', err);
}
}