Skip to content

Commit

Permalink
fix: Enable trustProxy for cloud-hosted Engines to correct IP allowli…
Browse files Browse the repository at this point in the history
…st (#699)

* fix: Enable trustProxy for cloud-hosted Engines to correct IP allowlist

* log unauthorized IP
  • Loading branch information
arcoraven authored Oct 3, 2024
1 parent a5b31d4 commit 7cd575b
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 32 deletions.
3 changes: 2 additions & 1 deletion src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ export const initServer = async () => {
connectionTimeout: SERVER_CONNECTION_TIMEOUT,
disableRequestLogging: true,
// env.TRUST_PROXY is used to determine if the X-Forwarded-For header should be trusted.
// This option is force enabled for cloud-hosted Engines.
// See: https://fastify.dev/docs/latest/Reference/Server/#trustproxy
trustProxy: env.TRUST_PROXY,
trustProxy: env.TRUST_PROXY || !!env.ENGINE_TIER,
...(env.ENABLE_HTTPS ? httpsObject : {}),
}).withTypeProvider<TypeBoxTypeProvider>();

Expand Down
37 changes: 25 additions & 12 deletions src/server/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { parseJWT } from "@thirdweb-dev/auth";
import {
ThirdwebAuth,
ThirdwebAuthUser,
getToken as getJWT,
type ThirdwebAuthUser,
} from "@thirdweb-dev/auth/fastify";
import { AsyncWallet } from "@thirdweb-dev/wallets/evm/wallets/async";
import { createHash } from "crypto";
import { FastifyInstance } from "fastify";
import { FastifyRequest } from "fastify/types/request";
import jsonwebtoken, { JwtPayload } from "jsonwebtoken";
import type { FastifyInstance } from "fastify";
import type { FastifyRequest } from "fastify/types/request";
import jsonwebtoken, { type JwtPayload } from "jsonwebtoken";
import { validate as uuidValidate } from "uuid";
import { getPermissions } from "../../db/permissions/getPermissions";
import { createToken } from "../../db/tokens/createToken";
Expand Down Expand Up @@ -87,7 +87,7 @@ export const withAuth = async (server: FastifyInstance) => {
await revokeToken({ id: payload.jti });
} catch {
logger({
service: "worker",
service: "server",
level: "error",
message: `[Auth] Failed to revoke token ${payload.jti}`,
});
Expand Down Expand Up @@ -267,10 +267,15 @@ const handleWebsocketAuth = async (

const isIpInAllowlist = await checkIpInAllowlist(req);
if (!isIpInAllowlist) {
logger({
service: "server",
level: "error",
message: `Unauthorized IP address: ${req.ip}`,
});
return {
isAuthed: false,
error:
"Unauthorized IP Address. See: https://portal.thirdweb.com/engine/features/security",
"Unauthorized IP address. See: https://portal.thirdweb.com/engine/features/security",
};
}

Expand Down Expand Up @@ -336,9 +341,14 @@ const handleKeypairAuth = async (args: {

const isIpInAllowlist = await checkIpInAllowlist(req);
if (!isIpInAllowlist) {
error =
"Unauthorized IP Address. See: https://portal.thirdweb.com/engine/features/security";
throw error;
logger({
service: "server",
level: "error",
message: `Unauthorized IP address: ${req.ip}`,
});
throw new Error(
"Unauthorized IP address. See: https://portal.thirdweb.com/engine/features/security",
);
}
return { isAuthed: true };
} catch (e) {
Expand Down Expand Up @@ -391,12 +401,16 @@ const handleAccessToken = async (
}

const isIpInAllowlist = await checkIpInAllowlist(req);

if (!isIpInAllowlist) {
logger({
service: "server",
level: "error",
message: `Unauthorized IP address: ${req.ip}`,
});
return {
isAuthed: false,
error:
"Unauthorized IP Address. See: https://portal.thirdweb.com/engine/features/security",
"Unauthorized IP address. See: https://portal.thirdweb.com/engine/features/security",
};
}

Expand Down Expand Up @@ -505,7 +519,6 @@ const hashRequestBody = (req: FastifyRequest): string => {
/**
* Check if the request IP is in the allowlist.
* Fetches cached config if available.
* env.TRUST_PROXY is used to determine if the X-Forwarded-For header should be trusted.
* @param req FastifyRequest
* @returns boolean
* @async
Expand Down
14 changes: 6 additions & 8 deletions src/server/routes/system/health.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Static, Type } from "@sinclair/typebox";
import { FastifyInstance } from "fastify";
import { Type, type Static } from "@sinclair/typebox";
import type { FastifyInstance } from "fastify";
import { StatusCodes } from "http-status-codes";
import { isDatabaseReachable } from "../../../db/client";
import { env } from "../../../utils/env";
import { isRedisReachable, redis } from "../../../utils/redis/redis";
import { isRedisReachable } from "../../../utils/redis/redis";
import { createCustomError } from "../../middleware/error";

type EngineFeature = "KEYPAIR_AUTH" | "CONTRACT_SUBSCRIPTIONS" | "IP_ALLOWLIST";
Expand Down Expand Up @@ -63,8 +63,8 @@ export async function healthCheck(fastify: FastifyInstance) {

res.status(StatusCodes.OK).send({
status: "OK",
engineVersion: process.env.ENGINE_VERSION,
engineTier: process.env.ENGINE_TIER ?? "SELF_HOSTED",
engineVersion: env.ENGINE_VERSION,
engineTier: env.ENGINE_TIER ?? "SELF_HOSTED",
features: getFeatures(),
});
},
Expand All @@ -73,11 +73,9 @@ export async function healthCheck(fastify: FastifyInstance) {

const getFeatures = (): EngineFeature[] => {
// IP Allowlist is always available as a feature, but added as a feature for backwards compatibility.
const features: EngineFeature[] = ["IP_ALLOWLIST"];
const features: EngineFeature[] = ["CONTRACT_SUBSCRIPTIONS", "IP_ALLOWLIST"];

if (env.ENABLE_KEYPAIR_AUTH) features.push("KEYPAIR_AUTH");
// Contract Subscriptions requires Redis.
if (redis) features.push("CONTRACT_SUBSCRIPTIONS");

return features;
};
19 changes: 8 additions & 11 deletions src/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@ export const UrlSchema = z
{ message: "Invalid URL" },
);

const boolSchema = (defaultBool: "true" | "false") =>
z
.string()
.default(defaultBool)
.refine((s) => s === "true" || s === "false", "must be 'true' or 'false'")
.transform((s) => s === "true");

export const env = createEnv({
server: {
NODE_ENV: z
Expand All @@ -48,6 +41,8 @@ export const env = createEnv({
.array(z.enum(["server", "worker", "cache", "websocket"]))
.parse(s.split(",")),
),
ENGINE_VERSION: z.string().optional(),
ENGINE_TIER: z.string().optional(),
THIRDWEB_API_SECRET_KEY: z.string().min(1),
ADMIN_WALLET_ADDRESS: z.string().min(1),
ENCRYPTION_PASSWORD: z.string().min(1),
Expand All @@ -58,15 +53,15 @@ export const env = createEnv({
),
PORT: z.coerce.number().default(3005),
HOST: z.string().default("0.0.0.0"),
ENABLE_HTTPS: boolSchema("false"),
ENABLE_HTTPS: z.coerce.boolean().default(false),
HTTPS_PASSPHRASE: z.string().default("thirdweb-engine"),
TRUST_PROXY: z.boolean().default(false),
TRUST_PROXY: z.coerce.boolean().default(false),
CLIENT_ANALYTICS_URL: z
.union([UrlSchema, z.literal("")])
.default("https://c.thirdweb.com/event"),
SDK_BATCH_TIME_LIMIT: z.coerce.number().default(0),
SDK_BATCH_SIZE_LIMIT: z.coerce.number().default(100),
ENABLE_KEYPAIR_AUTH: boolSchema("false"),
ENABLE_KEYPAIR_AUTH: z.coerce.boolean().default(false),
CONTRACT_SUBSCRIPTIONS_DELAY_SECONDS: z.coerce
.number()
.nonnegative()
Expand All @@ -82,7 +77,7 @@ export const env = createEnv({

// Prometheus
METRICS_PORT: z.coerce.number().default(4001),
METRICS_ENABLED: boolSchema("true"),
METRICS_ENABLED: z.coerce.boolean().default(true),

/**
* Limits
Expand All @@ -108,6 +103,8 @@ export const env = createEnv({
NODE_ENV: process.env.NODE_ENV,
LOG_LEVEL: process.env.LOG_LEVEL,
LOG_SERVICES: process.env.LOG_SERVICES,
ENGINE_VERSION: process.env.ENGINE_VERSION,
ENGINE_TIER: process.env.ENGINE_TIER,
THIRDWEB_API_SECRET_KEY: process.env.THIRDWEB_API_SECRET_KEY,
ADMIN_WALLET_ADDRESS: process.env.ADMIN_WALLET_ADDRESS,
ENCRYPTION_PASSWORD: process.env.ENCRYPTION_PASSWORD,
Expand Down

0 comments on commit 7cd575b

Please sign in to comment.