Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLS headers #1353

Merged
merged 16 commits into from
Sep 11, 2024
2 changes: 2 additions & 0 deletions dev/scripts/src/migrates/provider.db.2.0.1.to.2.0.2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const migrateUserCommitments = async () => {
dappAccount: dappContract,
datasetId,
ipAddress: "NO_IP_ADDRESS",
headers: {},
lastUpdatedTimestamp: requestedAtTimestamp,
providerAccount,
requestedAtTimestamp,
Expand Down Expand Up @@ -248,6 +249,7 @@ const migratePowCaptchas = async () => {
providerSignature: "NO_SIGNATURE_MIGRATED",
difficulty: 4,
ipAddress: "NO_IP_ADDRESS",
headers: {},
userSubmitted: true,
serverChecked: false,
};
Expand Down
28 changes: 14 additions & 14 deletions docker/docker-compose.provider.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ services:
- staging-x86_64
image: prosopo/provider:${COMPOSE_PROVIDER_IMAGE_VERSION}
labels:
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
pull_policy: always
env_file:
- ../.env.${NODE_ENV}
Expand All @@ -43,7 +43,7 @@ services:
max-size: '100m'
max-file: '1'
healthcheck:
test: ["CMD", "curl", "--fail", "localhost:9229/v1/prosopo/provider/status"] # ping the status api
test: ["CMD", "curl", "--fail", "localhost:9229/v1/prosopo/provider/status"] # ping the status api
interval: 30s
retries: 3
start_period: 30s
Expand Down Expand Up @@ -71,8 +71,8 @@ services:
- staging-arm64
image: mongodb-raspberrypi4-unofficial-r6.0.10
labels:
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
volumes:
- /data/db:/data/db
ports:
Expand All @@ -87,7 +87,7 @@ services:
max-size: '100m'
max-file: '1'
healthcheck:
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')", "--quiet"] # ping the mongo server
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')", "--quiet"] # ping the mongo server
interval: 30s
retries: 3
start_period: 30s
Expand All @@ -98,8 +98,8 @@ services:
- staging-x86_64
image: mongo:5.0.4
labels:
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
volumes:
- /data/db:/data/db
ports:
Expand All @@ -114,7 +114,7 @@ services:
max-size: '100m'
max-file: '1'
healthcheck:
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')", "--quiet"] # ping the mongo server
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')", "--quiet"] # ping the mongo server
interval: 30s
retries: 3
start_period: 30s
Expand All @@ -129,8 +129,8 @@ services:
env_file:
- ../.env.${NODE_ENV}
labels:
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
ports:
- '80:80'
- '443:443'
Expand All @@ -147,7 +147,7 @@ services:
max-size: '100m'
max-file: '1'
healthcheck:
test: ["CMD", "curl", "--fail", "localhost:2019/metrics"] # ping the caddy admin api
test: ["CMD", "curl", "--fail", "localhost:2019/metrics"] # ping the caddy admin api
interval: 30s
retries: 3
start_period: 30s
Expand All @@ -160,8 +160,8 @@ services:
- staging-arm64
image: containrrr/watchtower
labels:
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
- "com.centurylinklabs.watchtower.enable=true" # only services with this tag will be updated by watchtower
restart: unless-stopped # unless the container has been stopped, it will be restarted, even on reboot
env_file:
- ../.env.${NODE_ENV}
volumes:
Expand Down
32 changes: 31 additions & 1 deletion docker/provider.Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,37 @@
# reverse_proxy /metrics {$CADDY_ADMIN_API}

# reverse proxy to the provider container
reverse_proxy {$CADDY_PROVIDER_CONTAINER_NAME:provider}:{$CADDY_PROVIDER_PORT:9229}
reverse_proxy {$CADDY_PROVIDER_CONTAINER_NAME:provider}:{$CADDY_PROVIDER_PORT:9229} {
header_up X-TLS-Version "{tls_version}"
header_up X-TLS-Cipher "{tls_cipher}"
header_up X-TLS-Client-SNI "{tls_client_sni}"
header_up X-TLS-Client-Verified "{tls_client_verified}"
header_up X-TLS-Client-Cert-Subject "{tls_client_subject}"
header_up X-TLS-Client-Cert-Issuer "{tls_client_issuer}"
header_up X-TLS-Client-Cert-Serial "{tls_client_serial}"
header_up X-TLS-Client-Cert-Fingerprint "{tls_client_fingerprint}"
header_up X-Request-Start-Time "{start_time}"
header_up X-Request-ID "{request_id}"
header_up X-Scheme "{scheme}"
header_up X-Remote-Address "{remote}"
header_up X-Remote-IP "{remote_host}"
header_up X-Remote-Port "{remote_port}"
header_up X-Server-IP "{server_ip}"
header_up X-Server-Port "{server_port}"
header_up X-Elapsed-Time "{elapsed}"
header_up X-Request-Protocol "{proto}"
header_up X-Client-IP "{client_ip}"
header_up X-Host "{host}"
header_up X-HostPort "{hostport}"
header_up X-tls_client_certificate_der_base64 "{tls_client_certificate_der_base64}"
header_up X-tls_client_certificate_pem "{tls_client_certificate_pem}"
header_up X-upstream_hostport "{upstream_hostport}"
header_up X-http.request.uuid "{http.request.uuid}"
header_up X-http.request.tls.resumed "{http.request.tls.resumed}"
header_up X-http.request.tls.proto_mutual "{http.request.tls.proto_mutual}"
header_up X-http.request.tls.client.fingerprint "{http.request.tls.client.fingerprint}"
header_up X-http.request.tls.client.public_key "{http.request.tls.client.public_key}"
}

# logs. Note this is not limited, truncated or rotated whatsoever, so it grows over time!
log {
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"lint:license": "npm run -w @prosopo/scripts license",
"lint-fix:license": "npm run -w @prosopo/scripts license:fix",
"lint:dockerfile": "find . \\( -name 'Dockerfile' -o -name '*.dockerfile' \\) ! -path '*/node_modules/*' | xargs -P 1 --verbose -I {} bash -c 'docker run --rm -i hadolint/hadolint < {} || exit 255'",
"lint:actions": "docker run --rm -v $(pwd):/repo --workdir /repo rhysd/actionlint:latest -color",
"lint_disabled_:actions": "docker run --rm -v $(pwd):/repo --workdir /repo rhysd/actionlint:latest -color",
"lint:shell": "find . -name '*.sh' ! -path '*/node_modules/*' | xargs -P 1 --verbose -I {} bash -c \"shellcheck {} || exit 255\"",
"lint:yaml": "yamllint .",
"lint:js": "biome check .",
Expand Down Expand Up @@ -82,7 +82,11 @@
"node": ">=20",
"npm": ">=9"
},
"workspaces": ["dev/*", "packages/*", "demos/*"],
"workspaces": [
"dev/*",
"packages/*",
"demos/*"
],
"dependencies": {
"@prosopo/flux": "2.0.3"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/database/src/databases/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
type PendingCaptchaRequest,
type PoWChallengeComponents,
type PoWChallengeId,
type RequestHeaders,
type ScheduledTaskNames,
type ScheduledTaskResult,
type ScheduledTaskStatus,
Expand Down Expand Up @@ -497,6 +498,7 @@ export class ProviderDatabase extends MongoDatabase implements IDatabase {
difficulty: number,
providerSignature: string,
ipAddress: string,
headers: RequestHeaders,
serverChecked = false,
userSubmitted = false,
storedStatus: StoredStatus = StoredStatusNames.notStored,
Expand All @@ -508,6 +510,7 @@ export class ProviderDatabase extends MongoDatabase implements IDatabase {
challenge,
...components,
ipAddress,
headers,
result: { status: CaptchaStatus.pending },
userSubmitted,
serverChecked,
Expand Down
16 changes: 16 additions & 0 deletions packages/provider/src/api/captcha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ import { handleErrors } from "./errorHandler.js";

const NO_IP_ADDRESS = "NO_IP_ADDRESS" as const;

const flattenHeaders = (headers: {
[key: string]: string | string[] | undefined;
}) => {
// for each key/value pair in headers, if the value is an array, join it with a comma, if the value is undefined, return an empty string
return Object.fromEntries(
Object.entries(headers).map(([key, value]) => [
key,
Array.isArray(value) ? value.join(",") : value || "",
]),
);
};

/**
* Returns a router connected to the database which can interact with the Proposo protocol
*
Expand Down Expand Up @@ -65,6 +77,7 @@ export function prosopoRouter(env: ProviderEnvironment): Router {
datasetId,
user,
req.ip || NO_IP_ADDRESS,
flattenHeaders(req.headers),
);
const captchaResponse: CaptchaResponseBody = {
[ApiParams.captchas]: taskData.captchas.map((captcha: Captcha) => ({
Expand Down Expand Up @@ -124,6 +137,7 @@ export function prosopoRouter(env: ProviderEnvironment): Router {
Number.parseInt(parsed[ApiParams.timestamp]),
parsed[ApiParams.signature].provider.requestHash,
req.ip || NO_IP_ADDRESS,
flattenHeaders(req.headers),
);

const returnValue: CaptchaSolutionResponse = {
Expand Down Expand Up @@ -177,6 +191,7 @@ export function prosopoRouter(env: ProviderEnvironment): Router {
challenge.difficulty,
challenge.providerSignature,
req.ip || NO_IP_ADDRESS,
flattenHeaders(req.headers),
);

const getPowCaptchaResponse: GetPowCaptchaResponse = {
Expand Down Expand Up @@ -222,6 +237,7 @@ export function prosopoRouter(env: ProviderEnvironment): Router {
verifiedTimeout,
signature.user.timestamp,
req.ip || NO_IP_ADDRESS,
flattenHeaders(req.headers),
);
const response: PowCaptchaSolutionResponse = { verified };
return res.json(response);
Expand Down
8 changes: 4 additions & 4 deletions packages/provider/src/tasks/client/clientTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ export class ClientTaskManager {
},
},
);
} catch (e: any) {
} catch (e: unknown) {
this.logger.error(e);
await this.providerDB.updateScheduledTaskStatus(
taskID,
ScheduledTaskStatus.Failed,
{ error: e.toString() },
{ error: String(e) },
);
}
}
Expand Down Expand Up @@ -186,12 +186,12 @@ export class ClientTaskManager {
},
},
);
} catch (e: any) {
} catch (e: unknown) {
this.logger.error(e);
await this.providerDB.updateScheduledTaskStatus(
taskID,
ScheduledTaskStatus.Failed,
{ error: e.toString() },
{ error: String(e) },
);
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/provider/src/tasks/imgCaptcha/imgCaptchaTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
type DappUserSolutionResult,
type Hash,
type PendingCaptchaRequest,
type RequestHeaders,
} from "@prosopo/types";
import type {
IProviderDatabase,
Expand Down Expand Up @@ -81,6 +82,7 @@ export class ImgCaptchaManager {
datasetId: string,
userAccount: string,
ipAddress: string,
headers: RequestHeaders,
): Promise<{
captchas: Captcha[];
requestHash: string;
Expand Down Expand Up @@ -145,6 +147,7 @@ export class ImgCaptchaManager {
deadlineTs,
currentTime,
ipAddress,
headers,
);
return {
captchas,
Expand Down Expand Up @@ -175,6 +178,7 @@ export class ImgCaptchaManager {
timestamp: number,
providerRequestHashSignature: string,
ipAddress: string,
headers: RequestHeaders,
): Promise<DappUserSolutionResult> {
// check that the signature is valid (i.e. the user has signed the request hash with their private key, proving they own their account)
const verification = signatureVerify(
Expand Down Expand Up @@ -253,6 +257,7 @@ export class ImgCaptchaManager {
serverChecked: false,
requestedAtTimestamp: timestamp,
ipAddress,
headers,
};
await this.db.storeDappUserSolution(receivedCaptchas, commit);

Expand Down
2 changes: 2 additions & 0 deletions packages/provider/src/tasks/powCaptcha/powTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
POW_SEPARATOR,
type PoWCaptcha,
type PoWChallengeId,
type RequestHeaders,
} from "@prosopo/types";
import type { IProviderDatabase } from "@prosopo/types-database";
import { at, verifyRecency } from "@prosopo/util";
Expand Down Expand Up @@ -84,6 +85,7 @@ export class PowCaptchaManager {
timeout: number,
userTimestampSignature: string,
ipAddress: string,
headers: RequestHeaders,
): Promise<boolean> {
// Check signatures before doing DB reads to avoid unnecessary network connections
checkPowSignature(
Expand Down
Loading
Loading