Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.git
node_modules
npm-debug.log
yarn-debug.log
yarn-error.log
.env
*.md
.DS_Store
.vscode
.idea
*.log
coverage/
test/
__tests__/
*.test.*
94 changes: 94 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# syntax=docker/dockerfile:1.4

# Build stage
FROM node:22.1.0-alpine@sha256:487dc5d5122d578e13f2231aa4ac0f63068becd921099c4c677c850df93bede8 AS builder

# Set build-time variables for reproducibility
ARG NODE_ENV=development
ARG BUILD_VERSION=dev
ARG BUILD_DATE=unknown
ARG VCS_REF=unknown
ARG PORT=3081

# Set environment variables
ENV NODE_ENV=${NODE_ENV} \
NODE_VERSION=22.1.0

# Set build-time labels
LABEL org.opencontainers.image.created=${BUILD_DATE} \
org.opencontainers.image.version=${BUILD_VERSION} \
org.opencontainers.image.revision=${VCS_REF}

# Set consistent timezone and locale
ENV TZ=UTC \
LANG=C.UTF-8

# Create app directory
WORKDIR /usr/src/app

# Install build dependencies
RUN --mount=type=cache,target=/var/cache/apk \
apk add --no-cache \
python3 \
make \
g++ \
gcc \
linux-headers

# Copy dependency files
COPY package.json yarn.lock ./

# Install dependencies with cache mount
RUN --mount=type=cache,target=/usr/src/app/.yarn-cache \
yarn install --frozen-lockfile --production=false --cache-folder /usr/src/app/.yarn-cache && \
yarn cache clean && \
rm -rf /usr/src/app/.yarn-cache/*

# Copy source code
COPY . .

# Build TypeScript code with deterministic output
RUN yarn build

FROM node:22.1.0-alpine@sha256:487dc5d5122d578e13f2231aa4ac0f63068becd921099c4c677c850df93bede8 AS production

# Declare build arguments in production stage
ARG PORT=3081
ARG NODE_ENV=development

# Set build-time labels
LABEL org.opencontainers.image.created=${BUILD_DATE} \
org.opencontainers.image.version=${BUILD_VERSION} \
org.opencontainers.image.revision=${VCS_REF}

# Set runtime environment
ENV NODE_ENV=${NODE_ENV} \
PORT=${PORT} \
TZ=UTC \
LANG=C.UTF-8

WORKDIR /usr/src/app

# Create non-root user, certificate directory and logs directory
RUN addgroup -S bitgo && \
adduser -S bitgo -G bitgo && \
mkdir -p /app/certs && \
mkdir -p /usr/src/app/logs && \
chown -R bitgo:bitgo /app/certs && \
chown -R bitgo:bitgo /usr/src/app && \
chmod 750 /app/certs && \
chmod 750 /usr/src/app/logs

# Copy only necessary files from builder
COPY --from=builder --chown=bitgo:bitgo /usr/src/app/dist ./dist
COPY --from=builder --chown=bitgo:bitgo /usr/src/app/node_modules ./node_modules
COPY --from=builder --chown=bitgo:bitgo /usr/src/app/bin ./bin
COPY --from=builder --chown=bitgo:bitgo /usr/src/app/package.json .

USER bitgo

# Expose port from build arg
EXPOSE ${PORT}

Copilot AI Jun 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PORT build argument isn't declared or exported in the production stage, so EXPOSE ${PORT} may not work as expected. Consider adding ARG PORT and ENV PORT=${PORT} in the production stage.

Copilot uses AI. Check for mistakes.

# Start the application using the binary
CMD ["./bin/enclaved-bitgo-express"]
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,74 @@ ALLOW_SELF_SIGNED=false \
yarn start
```

## Container Deployment with Podman

First, build the container image:

```bash
# For Master Express (default port 3081)
yarn container:build

# For Enclaved Express (port 3080)
yarn container:build --build-arg PORT=3080
```

For local development, you'll need to run both the Enclaved Express and Master Express containers:

```bash
# Start Enclaved Express container
podman run -d \
-p 3080:3080 \
-v $(pwd)/certs:/app/certs:Z \
-e APP_MODE=enclaved \
-e BIND=0.0.0.0 \
-e TLS_MODE=mtls \
-e TLS_KEY_PATH=/app/certs/enclaved-express-key.pem \
-e TLS_CERT_PATH=/app/certs/enclaved-express-cert.pem \
-e KMS_URL=host.containers.internal:3000 \
-e NODE_ENV=development \
-e ALLOW_SELF_SIGNED=true \
bitgo-onprem-express

# View logs
podman logs -f <container_id>

# Test the endpoint (note: using https)
curl -k -X POST https://localhost:3080/ping

# Start Master Express container
podman run -d \
-p 3081:3081 \
-v $(pwd)/certs:/app/certs:Z \
-e APP_MODE=master-express \
-e BIND=0.0.0.0 \
-e TLS_MODE=mtls \
-e TLS_KEY_PATH=/app/certs/test-ssl-key.pem \
-e TLS_CERT_PATH=/app/certs/test-ssl-cert.pem \
-e ENCLAVED_EXPRESS_URL=https://host.containers.internal:3080 \
-e ENCLAVED_EXPRESS_CERT=/app/certs/enclaved-express-cert.pem \
-e ALLOW_SELF_SIGNED=true \
bitgo-onprem-express

# View logs
podman logs -f <container_id>

# Test the endpoints (note: using https and mTLS)
# For Enclaved Express
curl -k --cert certs/test-ssl-cert.pem --key certs/enclaved-express-key.pem -X POST https://localhost:3080/ping

# For Master Express
curl -k --cert certs/test-ssl-cert.pem --key certs/test-ssl-key.pem -X POST https://localhost:3081/ping

# Test the connection
curl -k -X POST https://localhost:3081/ping/enclavedExpress
```

Notes:
- `host.containers.internal` is a special DNS name that resolves to the host machine from inside containers
- The `:Z` option in volume mounts is specific to SELinux-enabled systems and ensures proper volume labeling
- The logs directory will be created with appropriate permissions if it doesn't exist

## API Endpoints

### Enclaved Express (Port 3080)
Expand Down
19 changes: 19 additions & 0 deletions certs/enclaved-express-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUbE+vqSu9IgPoLJncJqX5aiXh2GkwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDUzMDIwMDgxNVoXDTI2MDUz
MDIwMDgxNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAsK1g8ts/QRdEHVnmiZSijvtKl08yf13JWY0yksJW0O6x
mrt2uotvONxMNKhGtS+hPjcJ2OC7fCyift8oaCDs7PfIXjVNcN2zRKPci8ihNWvQ
XrYGLTvL9EVHpH7CdlJU43BTaeFusH+k/qv2pW5WQnz13ULdq7yvnDFvJAeahm9X
ptvr9RX9f8Aki0Y82Zi04PCiaHdqBPPl1OfHi+brf4xl7pQUq7Pub94/IDywe+QK
lGFPQ0exSVm5X/7hWv/AxqEFa/Bqb6Uw0qatVqhrgLEHlLUYVXs9NDNXm+865+aT
kvW2dnBpTVRZjnXO+N+BwSj+PfI28RqMXsmIhraN4QIDAQABo1MwUTAdBgNVHQ4E
FgQUnsZxpWiuxqDq/1kV12rMos4NN/cwHwYDVR0jBBgwFoAUnsZxpWiuxqDq/1kV
12rMos4NN/cwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAsAX4
CCEsIVrKQKJKluEDqOFiuKg0SSe4xVqlSW9vy9z3UYLfOpw14EtB6Lzbtgw7z47w
AnZZS99Zzn3tbYd06/X+b3jThF5TU1gqBcYDCC9HCd9xpmQEC7Ss1Xa88ubjuh+U
E/7xN5xRt85S07VihJWscfY7JCUAELBo3gDCZLfgHjw8xMfPRceE36rkc5B2p60b
WEmmOBWjrSboMOfocasBTUVUMDvGgmxEGEmKgTYshr5lWKcIteisbZi7+OZlkflp
PUZNu5DUyQyjftr2EShndaceZrjgXt6ezoyQBVgPRA+N+NAJn+uBr3B3nZZb/mft
n3XsbtsAAoU49kEVOg==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions certs/enclaved-express-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
Comment thread
mohammadalfaiyazbitgo marked this conversation as resolved.

Copilot AI Jun 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This private key is also committed to version control. For security, keys should be generated or injected at runtime rather than stored in the repo.

Copilot uses AI. Check for mistakes.
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwrWDy2z9BF0Qd
WeaJlKKO+0qXTzJ/XclZjTKSwlbQ7rGau3a6i2843Ew0qEa1L6E+NwnY4Lt8LKJ+
3yhoIOzs98heNU1w3bNEo9yLyKE1a9BetgYtO8v0RUekfsJ2UlTjcFNp4W6wf6T+
q/alblZCfPXdQt2rvK+cMW8kB5qGb1em2+v1Ff1/wCSLRjzZmLTg8KJod2oE8+XU
58eL5ut/jGXulBSrs+5v3j8gPLB75AqUYU9DR7FJWblf/uFa/8DGoQVr8GpvpTDS
pq1WqGuAsQeUtRhVez00M1eb7zrn5pOS9bZ2cGlNVFmOdc7434HBKP498jbxGoxe
yYiGto3hAgMBAAECggEACrbTnJwBJQBf3WvN7Y/5n7Qg3ODXo3Ow5Iu0gm6N9z5S
akYYuCKr4bCHtTXIkUT3K/UIgCzOEdoJwf85zb7EFMbIpuCSoVfrKYF1EXZe7a9r
w81EE0rUs9aJDAeyi/Gy5iwHUvIcf/rtqugLcr194QBU+fsLwwC+oY6POonKLCwg
gXMxRJKx+tvp86x6s7FU8+vi40L/mGbCC1Bl1YqraVp8nT17ICivlcHVZESxCcKN
tCAY+xKK+zH+s5sHAQvM4OlGEvCeT1VISlw/VqkODxcGzMGUc+mbnGtWvDcYm9Pa
54F29QapkdUwBVnucpTICenaMrLr19H9l6Zgvfvx1QKBgQDnHHeGnSD1bpZUhIUt
2vIQpj7o26zsx472h9PmqIZwODpcYSfw8MknymXVnL78gdHqVDL0mgj59zemUpC7
CR9RJAlV7/3TghUPdDFQ/SGj9+xGG/L/HNyy6bQeLIZiGOlURcdFqAtKCIk+51oK
eTDCOuy3Ijrq6F6FbYnkXHmwcwKBgQDDtDdCo9EjnyiZ1qGOx5jLRNbGVlN77QFS
tSmegODAwfLQpm4c4fE2WnzeWlNXnzs8GRLSRASXIYunjQdvgpX1KTpffZPoSP3k
tvL8cbh1zk7X8cmvkrVJjcpn/ecWPgeHGV6MjuhxqhaVoEMjFsKRqQaSQ7r/gZsm
Vba82tuXWwKBgHFlSlBGcJF7/U7i5uWk8/ivWVav0p0rHT5hTttx/OS68ge5s/tI
aaqYaHbzPdJvcCvlvEq/+X+MiUWWZWUgCLmrUNlVs9k/jk3S2Q+/4+2sC8YqmIQM
CU3P1YyolBc12eZ7hlbrKP7eSVkP8uIIrJ/ggZ0psnboJNia8nmV1i95AoGAPNWE
Z/6sQDp1UHzbc5qv8F/Rs42aHeeqhZ8y9MZzFvgzFpDloazKYm72adgCGDazHxdc
NmhWVPRkiQzZxtv86VyLfKt4krg91B7aoYZoJJahA5dxblZYbCjbRkAy2UMm6+QC
9AZoUwzgQFq1A+9LRCQamtTbCBmttNjoGQSfRgkCgYEAi03ZXB+B0/4C2HRUz/GQ
6moLgB7FzC4MLY2KUDeiP+3zBPnfbGQM0OgPYu7OOWPC6lebS4C6DuPCTOSW1z8u
f4FeVSKGrofPx+DmEvUMsUQ5TvjRPwNL40PVlrdxytZi6nV01GveScPfljvQUd2r
DRaZg+YgV9Yl6wi8y2G5RD0=
-----END PRIVATE KEY-----
19 changes: 19 additions & 0 deletions certs/test-ssl-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUYN0EUBLwq7uoLwDuTx7gDW0HS2UwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDUxNDIxMTUzMVoXDTI2MDUx
NDIxMTUzMVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAmxdCF7xCJcE6yyG+wrdhHMwRiQxbhDLW/hiKQcO/nn7z
QMM9LAV04+WG5LCmm0ygocbfXXhfWn5D6SSSESKTb8dbSj6AJbLSmYjA5vDSzh9R
rO9GzNTDCDne+epo+rN4BOjGh7K83naLei/bEfmklMp7x+TyoBC8Ps/3Eq4HOJfd
UzgV2L3oC/4dCbAnkgK2zanL8KEaH6aM0HytIaqMFYLBs2t8s7HHHcSEadHfjlJu
GwTmTS0nVhJBWYJvF6Pv/SwLFuSo93TJybaMMUSF3oJK35NEYXg6EtibJLUC9RvX
FVLytRA5z+x7FBnGBdi4ctMseecokV4u15ePCB3MXwIDAQABo1MwUTAdBgNVHQ4E
FgQUxHrQZFFBfTfuXeOOoXmHZQ8E6rswHwYDVR0jBBgwFoAUxHrQZFFBfTfuXeOO
oXmHZQ8E6rswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAj0hy
bwWh2B26cR0ADhuDC0MtGPBH0BrHypJEZ96nUPTY4oxRrjZusgdq60ooqsNyIW6k
cZHkajC/O0V6hnV4yMhLE8ZwA+31iyQakonpT5N1OON4Ddv9Bfvx8xOn75x/+RP+
GlAa31XxivryIi/5y7MEI0PwU34T/6bbMWdBaFRQtbuIXJ/90AZ0fBwIV0vJWjaO
1DriZAJe7hl63ZUw6CsfutpoyKkanF5GQB2CpolR3t1oeHwuDbZ550p1g2XFB6UI
9W+zlggQFeAnthzMoi3erO4sQ3j2b15QLZbk1HXHZcn3+89QcvdUcpG0u51bZdFW
SJ+bnT3TZ9H+szoa1w==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions certs/test-ssl-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
Comment thread
mohammadalfaiyazbitgo marked this conversation as resolved.

Copilot AI Jun 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Committing private keys into the repository can lead to security risks. Consider generating these keys at build or runtime, or storing them in a secure secrets store instead of version control.

Copilot uses AI. Check for mistakes.
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbF0IXvEIlwTrL
Ib7Ct2EczBGJDFuEMtb+GIpBw7+efvNAwz0sBXTj5YbksKabTKChxt9deF9afkPp
JJIRIpNvx1tKPoAlstKZiMDm8NLOH1Gs70bM1MMIOd756mj6s3gE6MaHsrzedot6
L9sR+aSUynvH5PKgELw+z/cSrgc4l91TOBXYvegL/h0JsCeSArbNqcvwoRofpozQ
fK0hqowVgsGza3yzsccdxIRp0d+OUm4bBOZNLSdWEkFZgm8Xo+/9LAsW5Kj3dMnJ
towxRIXegkrfk0RheDoS2JsktQL1G9cVUvK1EDnP7HsUGcYF2Lhy0yx55yiRXi7X
l48IHcxfAgMBAAECggEAAft4dHXgi+ZTX7iiXTobe1MUakxbzcMZ7QzX6jfxTA+o
APeTNuvURFFxDvKUaT8VJ9wzNgOi3F+UHffCB4a0nGTPmDxXm6O/KLKPHKSOso8Y
ln2cdGPHy7l0TdIe3g113EI0FL9GcLrSf5D7Bi4gWhKDJdlETKLKH9dn+2IkD3zE
VM+7pwqYV6XZu2GuZ1om1JE+Hx2D5YLIuRCON0RKpzCLikmM7VErA7sjy7LsCSh9
ty5n1s9GoNtF+YuOD9WeMWGDonMyJdYWTsmFYoT+LF7W+GHoEwvYm9595QuNnxVx
KQ4P5oKm/EfcSiBhCC8BdCIGch9tQPT7c/syVhHG4QKBgQDTdUVW7rJNnLtHgWF2
ubjh9b3ZfxPuTEu6ueKON5XXvSgfsMBNgCxwkemefGJ6xIjDu+swud+2H33Tqj23
GMMTZ1JEzNYINO1m/laSAK+DcL81q4sLLlJTbBhYeE6FBeEO1hAmU2IiYrgU6zbO
eyo4ysXtFJdnHSR9PHjZpt/cMQKBgQC7wmxw2UNPlmwTzl6l7z5DqYxizIfezb9l
pIYrmcD92asxZKPi1soz8PcOn30gGmjtZn/7FkXFHSRsNknUmqJOEbZrvNCcKndz
O+RbKGs8FAKlyog8k3CTToAng4PutsYrAuK/kx84P9FPCgTxdhejRMkfkSebCJQH
fmXRnlRdjwKBgQCm2Drz0rcBIg9q5hz+zp+gOoOnnusc9TozhQPLbvReGzQTfSTe
gamO0LJiiIYzk+rNdfKmqaJoUwS3A/ZaB8G0B6wT+QNPymMfBsNLxBq4PTfBoy68
jboLdJjpBVP/BZqEWEa51sTxmK7iYo0F8oxn7yaoX7zucUIfRp2cLl0noQKBgB+K
RG8cgAshiJw3IX0cWEhDdfquwvAxfcJURdmTJXE/HFvavREA5cyd4NKLBhjbdt7S
RhNmpWe8Qn8PC430P+l/XjZw7FYfaBtqZyzM+F6KOfuhrwsF9XY5TJvWotX5zAYz
oOVvkGIBjmaJl1T8cnIRvvtXheCsKzmrCO2SfDePAoGAM1ToKwXSeZEm2Kyf/PV9
74lvBKYP5LP+pSmrcTq9jUbPQy3KmlhBi+kyhVK+2Awmh0J9tzu83C8lWR25L4mc
/Uwjhv2KwmvJKyZ4/5t/oMZ+BsZERSHj39juLNW+UL82M2heM5tv5/MI779SAmzl
VJMN4N1x+L7408dEGu0j2ds=
-----END PRIVATE KEY-----
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"test:coverage": "nyc mocha --require ts-node/register 'src/**/__tests__/**/*.test.ts'",
"lint": "eslint --quiet .",
"generate-test-ssl": "openssl req -x509 -newkey rsa:2048 -keyout test-ssl-key.pem -out test-ssl-cert.pem -days 365 -nodes -subj '/CN=localhost'",
"generate:openapi:masterExpress": "npx @api-ts/openapi-generator --name @bitgo/master-bitgo-express ./src/masterBitgoExpress/routers/index.ts > masterBitgoExpress.json"
"generate:openapi:masterExpress": "npx @api-ts/openapi-generator --name @bitgo/master-bitgo-express ./src/masterBitgoExpress/routers/index.ts > masterBitgoExpress.json",
"container:build": "podman build -t bitgo-onprem-express ."
},
"dependencies": {
"@api-ts/io-ts-http": "^3.2.1",
Expand Down
8 changes: 5 additions & 3 deletions src/masterBitgoExpress/routers/enclavedExpressHealth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import { responseHandler } from '../../shared/middleware';
const PingEnclavedResponse: HttpResponse = {
200: t.type({
status: t.string,
// TODO: Move to common definition between enclavedExpress and masterExpress
enclavedResponse: t.type({
message: t.string,
status: t.string,
timestamp: t.string,
}),
}),
Expand Down Expand Up @@ -79,7 +78,10 @@ export function createEnclavedExpressRouter(

return Response.ok({
status: 'Successfully pinged enclaved express',
enclavedResponse: response.body,
enclavedResponse: {
status: response.body.status,
timestamp: response.body.timestamp,
},
});
} catch (error) {
logger.error('Failed to ping enclaved express:', { error });
Expand Down