Skip to content

Commit

Permalink
feat(microservice): add support for dealing with cookies as a prepara…
Browse files Browse the repository at this point in the history
…tion for allowing to login into a c8y edge instance

Signed-off-by: Tristan Bastian <tristan.bastian@softwareag.com>
  • Loading branch information
reey committed Apr 3, 2024
1 parent 0789e67 commit b789902
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 16 deletions.
14 changes: 11 additions & 3 deletions backend/src/header-adjustment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ConnectionDetails } from "./connection-details";
import { IncomingHttpHeaders } from "http";
import { name, version } from "../package.json";
import * as cookieLib from "cookie-parse";

export class HeaderAdjustment {
private static headersToRemove = [];
Expand Down Expand Up @@ -30,6 +31,12 @@ export class HeaderAdjustment {
const newKey = key.replace(this.headerPrefix, "").replace(suffix, "");
keysToAdd[newKey] = value;
}
if (headers.cookie) {
const {'XSRF-TOKEN': xsrf} = cookieLib.parse(headers.cookie || "");
if (xsrf) {
keysToAdd['x-xsrf-token'] = xsrf;
}
}
Object.assign(headers, keysToAdd);
}

Expand All @@ -56,13 +63,13 @@ export class HeaderAdjustment {

// we want to keep the authorization header if the actual auth was cookie based
// but we want to remove the fake basic auth header added by the microservice proxy
if (!headers.authorization?.startsWith('Basic ')) {
if (!headers.authorization?.startsWith("Basic ")) {
return;
}

const token = headers.authorization.replace(/^Basic\s/, "");
const decodedToken = Buffer.from(token, "base64").toString("utf-8");
if (decodedToken.endsWith(':<fake password>')) {
if (decodedToken.endsWith(":<fake password>")) {
delete headers.authorization;
}
}
Expand All @@ -73,9 +80,10 @@ export class HeaderAdjustment {
}

const cookieKeysToReplace = ["authorization", "XSRF-TOKEN"];
return cookieKeysToReplace.reduceRight((prev, curr) => {
const replacedCookies = cookieKeysToReplace.reduceRight((prev, curr) => {
return this.removeCookieByName(prev, curr);
}, currentCookieValue);
return replacedCookies.replaceAll("cloud-http-proxy-", "");
}

private static removeCookieByName(cookieValue: string, name: string) {
Expand Down
60 changes: 47 additions & 13 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HeaderAdjustment } from "./header-adjustment";
import { RCAServerStore } from "./rca-server-store";
import Agent from "agentkeepalive";
import { HttpsAgent } from "agentkeepalive";
import * as http from "http";

dotenv.config();

Expand Down Expand Up @@ -45,6 +46,14 @@ app.get("/health", (req, res) => {
});
});

app.use((req, res, next) => {
if (req.headers.authorization || req.headers.cookie?.includes('authorization')) {
return next();
}
res.setHeader('WWW-Authenticate', 'Basic realm="My Realm"')
res.status(401).send();
});

async function getTarget(
req: express.Request<
{
Expand All @@ -63,6 +72,7 @@ async function getTarget(
} catch (e) {
requestLogger.error("Failed to retrieve details", {
e,
errorMessage: e.errorMessage,
headers: req.headers,
});
throw e;
Expand Down Expand Up @@ -96,38 +106,57 @@ function hostRewrite(req: express.Request<any>, secure: boolean) {

function getRewriteOptions(
req: express.Request<any>,
secure?: boolean
secure?: boolean,
hasCustomHost?: boolean
): Server.ServerOptions {
const { "x-forwarded-host": forwardedHost } = req.headers;
return {
autoRewrite: false,
hostRewrite: hostRewrite(req, secure),
changeOrigin: true,
changeOrigin: !hasCustomHost,
protocolRewrite: (req.headers["x-forwarded-proto"] as string) || "http",
cookieDomainRewrite:
typeof forwardedHost === "string" ? `.${forwardedHost.replace(/:.*$/, '')}` : undefined,
cookiePathRewrite: `/service/cloud-http-proxy${secure ? '/s' : ''}/${req.params.cloudProxyDeviceId}/${req.params.cloudProxyConfigId}/`,
};
}

app.use((req, res, next) => {
if (req.headers.authorization || req.headers.cookie?.includes('authorization')) {
return next();
}
res.setHeader('WWW-Authenticate', 'Basic realm="My Realm"')
res.status(401).send();
})
function hasCustomHostHeader(req: express.Request, deviceId: string, configId: string) {
const headerToLookoutFor = `rca-http-header-host-${deviceId}-${configId}`;
return !!req.headers[headerToLookoutFor];
}

function prefixCookiesToBeSet(proxy: Server<http.IncomingMessage, http.ServerResponse<http.IncomingMessage>>) {
proxy.on('proxyRes', (response) => {
const cookiesToSet = response.headers["set-cookie"];
if (!cookiesToSet?.length) {
return;
}

const adjustedCookies = cookiesToSet.map((cookie: string) => {
return `cloud-http-proxy-${cookie}`;
});

response.headers["set-cookie"] = adjustedCookies;
});
}

app.use("/s/:cloudProxyDeviceId/:cloudProxyConfigId/", async (req, res) => {
const requestLogger = logger.child({
method: req.method,
url: req.url,
params: req.params,
});

try {
const hasCustomHost = hasCustomHostHeader(req, req.params.cloudProxyDeviceId, req.params.cloudProxyConfigId);
const target = await getTarget(req, requestLogger, true);
const rewrietOptions = getRewriteOptions(req, true);
const rewriteOptions = getRewriteOptions(req, true, hasCustomHost);
const proxy = createProxyServer({
target,
agent: agents.https,
secure: false,
...rewrietOptions,
...rewriteOptions,
});

if (req.headers.upgrade) {
Expand All @@ -138,6 +167,8 @@ app.use("/s/:cloudProxyDeviceId/:cloudProxyConfigId/", async (req, res) => {
return;
}

prefixCookiesToBeSet(proxy);

proxy.web(
req,
res,
Expand Down Expand Up @@ -166,12 +197,13 @@ app.use("/:cloudProxyDeviceId/:cloudProxyConfigId/", async (req, res) => {
});

try {
const hasCustomHost = hasCustomHostHeader(req, req.params.cloudProxyDeviceId, req.params.cloudProxyConfigId);
const target = await getTarget(req, requestLogger);
const rewrietOptions = getRewriteOptions(req, true);
const rewriteOptions = getRewriteOptions(req, true, hasCustomHost);
const proxy = createProxyServer({
target,
agent: agents.http,
...rewrietOptions
...rewriteOptions,
});

if (req.headers.upgrade) {
Expand All @@ -182,6 +214,8 @@ app.use("/:cloudProxyDeviceId/:cloudProxyConfigId/", async (req, res) => {
return;
}

prefixCookiesToBeSet(proxy);

proxy.web(
req,
res,
Expand Down

0 comments on commit b789902

Please sign in to comment.