Skip to content

Commit 020ec0d

Browse files
feat(awm): backup awm client configurations
2 parents ade43be + 20071b7 commit 020ec0d

21 files changed

Lines changed: 1747 additions & 250 deletions

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ Key features include:
4141
- **Advanced Wallet Manager** (Port 3080) - An isolated signing server with no internet access that only connects to your key provider API implementation for key operations.
4242
- **Master Express** (Port 3081) - An API gateway providing end-to-end wallet creation and transaction support, integrating [BitGo APIs](https://developers.bitgo.com/reference/overview#/) with secure communication to Advanced Wallet Manager.
4343

44+
### User and Backup AWM Instances
45+
46+
Master Express supports configuring **separate Advanced Wallet Manager instances** for user and backup key operations. This allows you to isolate the user key and backup key into independent AWM deployments, each connected to its own KMS/HSM, for stronger security and operational separation.
47+
48+
- **User AWM** — Configured via `ADVANCED_WALLET_MANAGER_URL`. This is the primary AWM instance and handles all user key operations. It is always required.
49+
- **Backup AWM** — Configured via `ADVANCED_WALLET_MANAGER_BACKUP_URL`. This is an optional, separate AWM instance dedicated to backup key operations.
50+
51+
**Fallback behavior:** If `ADVANCED_WALLET_MANAGER_BACKUP_URL` is not set, backup key operations automatically fall back to the user AWM instance. This means a single AWM instance handles both user and backup keys — which is the default behavior and sufficient for most deployments.
52+
53+
When a backup AWM URL **is** provided, dedicated mTLS certificates for the backup AWM are also required (see [Backup AWM mTLS Settings](#backup-awm-mtls-settings) below). The backup AWM will not reuse the primary AWM's certificates.
54+
4455
## Installation
4556

4657
### Prerequisites
@@ -185,6 +196,16 @@ curl -X POST http://localhost:3081/ping/advancedWalletManager
185196
| `BITGO_AUTH_VERSION` | BitGo authentication version | `2` ||
186197
| `BITGO_CUSTOM_BITCOIN_NETWORK` | Custom Bitcoin network | - ||
187198

199+
### Backup AWM Settings (Optional)
200+
201+
These settings are only required when you want to use a **separate AWM instance for backup key operations**. If these are not configured, backup operations fall back to the primary (user) AWM instance.
202+
203+
| Variable | Description | Default | Required |
204+
| ------------------------------------- | ------------------------------------ | ------- | ------------------------------------- |
205+
| `ADVANCED_WALLET_MANAGER_BACKUP_URL` | Backup AWM URL | - | Only if using a separate backup AWM |
206+
207+
> **Note:** When `ADVANCED_WALLET_MANAGER_BACKUP_URL` is not set, the system uses the primary AWM (`ADVANCED_WALLET_MANAGER_URL`) for both user and backup key operations. This is the simplest configuration and works well when a single AWM instance manages both keys.
208+
188209
### Additional Settings
189210

190211
| Variable | Description | Default | Applies To |
@@ -233,6 +254,19 @@ curl -X POST http://localhost:3081/ping/advancedWalletManager
233254
| `AWM_SERVER_CA_CERT` | AWM server CA certificate (alternative) | PEM string |
234255
| `AWM_SERVER_CERT_ALLOW_SELF_SIGNED` | Allow self-signed AWM server certificates | Boolean (default: `false`) |
235256

257+
**For Master Express → Backup Advanced Wallet Manager (optional):** <a id="backup-awm-mtls-settings"></a>
258+
259+
These are only required when `ADVANCED_WALLET_MANAGER_BACKUP_URL` is set. If the backup URL is not configured, backup key operations use the primary AWM connection and these settings are ignored.
260+
261+
| Variable | Description | Format |
262+
| ----------------------------------------- | -------------------------------------------------- | -------------------------- |
263+
| `AWM_BACKUP_CLIENT_TLS_KEY_PATH` | Backup AWM client private key file path | File path |
264+
| `AWM_BACKUP_CLIENT_TLS_KEY` | Backup AWM client private key (alternative) | PEM string |
265+
| `AWM_BACKUP_CLIENT_TLS_CERT_PATH` | Backup AWM client certificate file path | File path |
266+
| `AWM_BACKUP_CLIENT_TLS_CERT` | Backup AWM client certificate (alternative) | PEM string |
267+
| `AWM_BACKUP_SERVER_CA_CERT_PATH` | Backup AWM server CA certificate file path | File path |
268+
| `AWM_BACKUP_SERVER_CA_CERT` | Backup AWM server CA certificate (alternative) | PEM string |
269+
236270
**For Advanced Wallet Manager → key provider:**
237271

238272
| Variable | Description | Format |
@@ -245,7 +279,7 @@ curl -X POST http://localhost:3081/ping/advancedWalletManager
245279
| `KEY_PROVIDER_SERVER_CA_CERT` | Key provider server CA certificate (alternative) | PEM string |
246280
| `KEY_PROVIDER_SERVER_CERT_ALLOW_SELF_SIGNED` | Allow self-signed key provider server certificates | Boolean (default: `false`) |
247281

248-
> **Note:** For security reasons, when `TLS_MODE=mtls`, outbound client certificates are required and cannot reuse server certificates. When `TLS_MODE=disabled`, these certificates aren't required.
282+
> **Note:** For security reasons, when `TLS_MODE=mtls`, outbound client certificates are required and cannot reuse server certificates. When a backup AWM is configured, it requires its own dedicated set of certificates — it will not reuse the primary AWM's certificates. When `TLS_MODE=disabled`, these certificates aren't required.
249283
250284
## Container Deployment with Podman
251285

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import 'should';
2+
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
3+
import {
4+
createAwmClient,
5+
createAwmBackupClient,
6+
} from '../../../masterBitgoExpress/clients/advancedWalletManagerClient';
7+
8+
describe('AWM Backup Client', () => {
9+
const baseConfig: MasterExpressConfig = {
10+
appMode: AppMode.MASTER_EXPRESS,
11+
port: 3081,
12+
bind: 'localhost',
13+
timeout: 60000,
14+
httpLoggerFile: '',
15+
env: 'test',
16+
disableEnvCheck: true,
17+
authVersion: 2,
18+
advancedWalletManagerUrl: 'http://primary-awm.invalid',
19+
awmServerCaCert: 'dummy-cert',
20+
tlsMode: TlsMode.DISABLED,
21+
clientCertAllowSelfSigned: true,
22+
};
23+
24+
describe('createAwmBackupClient', () => {
25+
it('should return undefined when no backup URL is configured', () => {
26+
const result = createAwmBackupClient(baseConfig, 'tbtc');
27+
(result === undefined).should.be.true();
28+
});
29+
30+
it('should create a client when backup URL is configured', () => {
31+
const config: MasterExpressConfig = {
32+
...baseConfig,
33+
advancedWalletManagerBackupUrl: 'http://backup-awm.invalid',
34+
};
35+
const result = createAwmBackupClient(config, 'tbtc');
36+
(result !== undefined).should.be.true();
37+
});
38+
39+
it('should create a client pointing to the backup URL, not the primary', () => {
40+
const config: MasterExpressConfig = {
41+
...baseConfig,
42+
advancedWalletManagerBackupUrl: 'http://backup-awm.invalid',
43+
};
44+
const backupClient = createAwmBackupClient(config, 'tbtc');
45+
const primaryClient = createAwmClient(config, 'tbtc');
46+
47+
// Both clients should exist
48+
(backupClient !== undefined).should.be.true();
49+
(primaryClient !== undefined).should.be.true();
50+
51+
// They should be different instances
52+
(backupClient !== primaryClient).should.be.true();
53+
});
54+
55+
it('should throw when backup URL is set with mTLS but backup server CA cert is missing', () => {
56+
const config: MasterExpressConfig = {
57+
...baseConfig,
58+
tlsMode: TlsMode.MTLS,
59+
advancedWalletManagerBackupUrl: 'https://backup-awm.invalid',
60+
awmServerCaCert: 'primary-ca-cert',
61+
awmClientTlsKey: 'primary-client-key',
62+
awmClientTlsCert: 'primary-client-cert',
63+
// No backup-specific certs — should NOT fall back to primary
64+
};
65+
(() => createAwmBackupClient(config, 'tbtc')).should.throw(
66+
/awmBackupServerCaCert is required/,
67+
);
68+
});
69+
70+
it('should throw when backup URL is set with mTLS but backup client certs are missing', () => {
71+
const config: MasterExpressConfig = {
72+
...baseConfig,
73+
tlsMode: TlsMode.MTLS,
74+
advancedWalletManagerBackupUrl: 'https://backup-awm.invalid',
75+
awmBackupServerCaCert: 'backup-ca-cert',
76+
// No backup client certs
77+
};
78+
(() => createAwmBackupClient(config, 'tbtc')).should.throw(
79+
/awmBackupClientTlsKey and awmBackupClientTlsCert are required/,
80+
);
81+
});
82+
83+
it('should create a client when all backup-specific certs are provided with mTLS', () => {
84+
const config: MasterExpressConfig = {
85+
...baseConfig,
86+
tlsMode: TlsMode.MTLS,
87+
advancedWalletManagerBackupUrl: 'https://backup-awm.invalid',
88+
awmServerCaCert: 'primary-ca-cert',
89+
awmClientTlsKey: 'primary-client-key',
90+
awmClientTlsCert: 'primary-client-cert',
91+
awmBackupServerCaCert: 'backup-ca-cert',
92+
awmBackupClientTlsKey: 'backup-client-key',
93+
awmBackupClientTlsCert: 'backup-client-cert',
94+
};
95+
const result = createAwmBackupClient(config, 'tbtc');
96+
(result !== undefined).should.be.true();
97+
});
98+
});
99+
100+
describe('fallback behavior in middleware', () => {
101+
it('should use primary client for both user and backup when no backup URL is set', () => {
102+
const primaryClient = createAwmClient(baseConfig, 'tbtc');
103+
const backupClient = createAwmBackupClient(baseConfig, 'tbtc');
104+
105+
(primaryClient !== undefined).should.be.true();
106+
// No backup URL → backup client is undefined → middleware falls back to primary
107+
(backupClient === undefined).should.be.true();
108+
109+
// Middleware would do: awmBackupClient = backupClient ?? primaryClient
110+
const effectiveBackupClient = backupClient ?? primaryClient;
111+
(effectiveBackupClient === primaryClient).should.be.true();
112+
});
113+
114+
it('should use separate client for backup when backup URL is set', () => {
115+
const config: MasterExpressConfig = {
116+
...baseConfig,
117+
advancedWalletManagerBackupUrl: 'http://backup-awm.invalid',
118+
};
119+
const primaryClient = createAwmClient(config, 'tbtc');
120+
const backupClient = createAwmBackupClient(config, 'tbtc');
121+
122+
(primaryClient !== undefined).should.be.true();
123+
(backupClient !== undefined).should.be.true();
124+
125+
// Middleware would do: awmBackupClient = backupClient ?? primaryClient
126+
const effectiveBackupClient = backupClient ?? primaryClient;
127+
(effectiveBackupClient === backupClient).should.be.true();
128+
(effectiveBackupClient !== primaryClient).should.be.true();
129+
});
130+
});
131+
});

0 commit comments

Comments
 (0)