gm-shield 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gm_shield-0.2.0/.github/workflows/ci.yaml +34 -0
- gm_shield-0.2.0/.github/workflows/publish.yml +23 -0
- gm_shield-0.2.0/.gitignore +42 -0
- gm_shield-0.2.0/CLAUDE.md +104 -0
- gm_shield-0.2.0/PKG-INFO +12 -0
- gm_shield-0.2.0/README.md +221 -0
- gm_shield-0.2.0/docs/ONBOARD.md +176 -0
- gm_shield-0.2.0/pyproject.toml +55 -0
- gm_shield-0.2.0/src/exchange_keyshare/__init__.py +3 -0
- gm_shield-0.2.0/src/exchange_keyshare/cfn.py +261 -0
- gm_shield-0.2.0/src/exchange_keyshare/cli.py +43 -0
- gm_shield-0.2.0/src/exchange_keyshare/commands/__init__.py +1 -0
- gm_shield-0.2.0/src/exchange_keyshare/commands/config.py +55 -0
- gm_shield-0.2.0/src/exchange_keyshare/commands/enclave.py +950 -0
- gm_shield-0.2.0/src/exchange_keyshare/commands/keys.py +476 -0
- gm_shield-0.2.0/src/exchange_keyshare/commands/setup.py +162 -0
- gm_shield-0.2.0/src/exchange_keyshare/commands/teardown.py +210 -0
- gm_shield-0.2.0/src/exchange_keyshare/config.py +175 -0
- gm_shield-0.2.0/src/exchange_keyshare/enclave.py +110 -0
- gm_shield-0.2.0/src/exchange_keyshare/keys.py +131 -0
- gm_shield-0.2.0/src/exchange_keyshare/schema.py +125 -0
- gm_shield-0.2.0/src/exchange_keyshare/setup.py +89 -0
- gm_shield-0.2.0/src/exchange_keyshare/templates/credentials-stack.yaml +137 -0
- gm_shield-0.2.0/src/exchange_keyshare/templates/enclave-stack.yaml +251 -0
- gm_shield-0.2.0/templates +1 -0
- gm_shield-0.2.0/tests/__init__.py +1 -0
- gm_shield-0.2.0/tests/test_cfn.py +54 -0
- gm_shield-0.2.0/tests/test_cli.py +320 -0
- gm_shield-0.2.0/tests/test_config.py +120 -0
- gm_shield-0.2.0/tests/test_enclave_commands.py +385 -0
- gm_shield-0.2.0/tests/test_enclave_policies.py +232 -0
- gm_shield-0.2.0/tests/test_keys.py +98 -0
- gm_shield-0.2.0/tests/test_schema.py +169 -0
- gm_shield-0.2.0/tests/test_setup.py +13 -0
- gm_shield-0.2.0/uv.lock +491 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, v2]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, v2]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
run: uv python install 3.12
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: uv sync --dev
|
|
23
|
+
|
|
24
|
+
- name: Lint
|
|
25
|
+
run: uv run ruff check src/
|
|
26
|
+
|
|
27
|
+
- name: Format
|
|
28
|
+
run: uv run ruff format --check src/
|
|
29
|
+
|
|
30
|
+
- name: Run type checker
|
|
31
|
+
run: uv run pyright
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: uv run pytest
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
contents: read
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Install uv
|
|
17
|
+
uses: astral-sh/setup-uv@v5
|
|
18
|
+
|
|
19
|
+
- name: Build package
|
|
20
|
+
run: uv build
|
|
21
|
+
|
|
22
|
+
- name: Publish to PyPI
|
|
23
|
+
run: uv publish
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
.eggs/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
|
|
17
|
+
# Testing
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# IDE
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
*.swp
|
|
26
|
+
*.swo
|
|
27
|
+
|
|
28
|
+
# OS
|
|
29
|
+
.DS_Store
|
|
30
|
+
Thumbs.db
|
|
31
|
+
|
|
32
|
+
# Local config (keep out of repo)
|
|
33
|
+
config.yaml
|
|
34
|
+
|
|
35
|
+
# Worktrees
|
|
36
|
+
.worktrees/
|
|
37
|
+
|
|
38
|
+
docs/plans
|
|
39
|
+
.envrc
|
|
40
|
+
|
|
41
|
+
.local
|
|
42
|
+
.mock
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# CLAUDE.md - gm-shield
|
|
2
|
+
|
|
3
|
+
CLI tool for market makers to securely share exchange API credentials.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- Python 3.12+, pyright strict mode
|
|
8
|
+
- Click (CLI), boto3 (AWS), rich (terminal UI), questionary (interactive prompts)
|
|
9
|
+
- uv (package manager)
|
|
10
|
+
|
|
11
|
+
## Project Structure
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
src/exchange_keyshare/
|
|
15
|
+
├── cli.py # CLI entrypoint
|
|
16
|
+
├── config.py # Config file (~/.config/gm-shield/config.yaml)
|
|
17
|
+
├── cfn.py # CloudFormation polling helpers
|
|
18
|
+
├── setup.py # Credentials stack creation
|
|
19
|
+
├── enclave.py # Enclave stack creation
|
|
20
|
+
├── keys.py # S3 credential operations
|
|
21
|
+
├── schema.py # Credential validation
|
|
22
|
+
├── templates/
|
|
23
|
+
│ ├── credentials-stack.yaml # CFN template: S3 bucket, KMS key, IAM role
|
|
24
|
+
│ └── enclave-stack.yaml # CFN template: EC2 + Nitro Enclave
|
|
25
|
+
└── commands/
|
|
26
|
+
├── setup.py # `setup` / `teardown` commands
|
|
27
|
+
├── teardown.py
|
|
28
|
+
├── config.py # `config` command
|
|
29
|
+
├── keys.py # `keys` subcommands
|
|
30
|
+
└── enclave.py # `enclave` subcommands
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
gm-shield setup # Deploy credentials stack (S3, KMS, IAM)
|
|
37
|
+
gm-shield teardown # Delete credentials stack
|
|
38
|
+
gm-shield config # Show current config
|
|
39
|
+
|
|
40
|
+
gm-shield keys list # List credentials
|
|
41
|
+
gm-shield keys create # Create credential (interactive)
|
|
42
|
+
gm-shield keys delete # Delete credential
|
|
43
|
+
gm-shield keys update # Update pairs/labels
|
|
44
|
+
|
|
45
|
+
gm-shield enclave approve # Grant enclave access (add KMS/S3 policy)
|
|
46
|
+
gm-shield enclave revoke # Revoke enclave access
|
|
47
|
+
gm-shield enclave rotate # Rotate PCR attestation values
|
|
48
|
+
# hidden: enclave deploy / enclave teardown
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
`~/.config/gm-shield/config.yaml` — written by `setup`, read by all other commands.
|
|
54
|
+
|
|
55
|
+
Credentials stack fields: `bucket`, `region`, `stack_name`, `stack_id`, `kms_key_arn`
|
|
56
|
+
|
|
57
|
+
Enclave stack fields (`enclave` key): `stack_name`, `stack_id`, `region`, `role_arn`, `instance_profile_arn`, `host_ref`, `eip`, `labels`
|
|
58
|
+
|
|
59
|
+
Approved enclave fields (`approved_enclave` key): `role_arn`, `pcr8`, `pcr0` (optional), `approved_at`
|
|
60
|
+
|
|
61
|
+
Override path: `GM_SHIELD_CONFIG` env var. Region fallback: `AWS_REGION` / `AWS_DEFAULT_REGION`.
|
|
62
|
+
|
|
63
|
+
## CloudFormation Stacks
|
|
64
|
+
|
|
65
|
+
**credentials-stack.yaml** (`setup` / `teardown`):
|
|
66
|
+
- S3 bucket (versioned, KMS-encrypted, access logged) + KMS key (auto-rotating)
|
|
67
|
+
- `ConsumerAccessRole` — read-only IAM role for credential consumer (external ID required)
|
|
68
|
+
- `DeletionPolicy: Retain` on bucket and KMS key
|
|
69
|
+
|
|
70
|
+
**enclave-stack.yaml** (`enclave deploy` / `enclave teardown`):
|
|
71
|
+
- EC2 instance with Nitro Enclave enabled, static EIP, locked-down security group
|
|
72
|
+
|
|
73
|
+
**Enclave attestation:** `approve` adds KMS + S3 policy statements scoped to the enclave's IAM role and PCR values (PCR8 required, PCR0 optional).
|
|
74
|
+
|
|
75
|
+
## Credential Schema
|
|
76
|
+
|
|
77
|
+
Stored as YAML in S3 under `exchange-credentials/` prefix:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
version: "1"
|
|
81
|
+
exchange: binance # binance, coinbase, kraken, kucoin, bitget, okx
|
|
82
|
+
credential:
|
|
83
|
+
api_key: "..."
|
|
84
|
+
api_secret: "..."
|
|
85
|
+
passphrase: "..." # required for coinbase, kucoin, bitget, okx
|
|
86
|
+
pairs: # optional, BASE/QUOTE format
|
|
87
|
+
- BTC/USDT
|
|
88
|
+
labels: # optional
|
|
89
|
+
- key: environment
|
|
90
|
+
value: production
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv sync
|
|
97
|
+
uv run pytest
|
|
98
|
+
uv run pyright
|
|
99
|
+
uv run ruff check src/
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Worktrees
|
|
103
|
+
|
|
104
|
+
Use `.worktrees/` for feature branches (already in `.gitignore`).
|
gm_shield-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gm-shield
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI for market makers to securely share exchange API credentials
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: boto3-stubs>=1.42.40
|
|
7
|
+
Requires-Dist: boto3>=1.42.80
|
|
8
|
+
Requires-Dist: botocore[crt]>=1.42.80
|
|
9
|
+
Requires-Dist: click>=8.1.0
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
Requires-Dist: questionary>=2.0.0
|
|
12
|
+
Requires-Dist: rich>=14.3.3
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# gm-shield
|
|
2
|
+
|
|
3
|
+
CLI tool for securely sharing exchange API credentials using Nitro Enclaves for isolated credential access.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
**Complete isolation:**
|
|
8
|
+
- Credentials are stored in S3 with KMS encryption
|
|
9
|
+
- Nitro Enclave runs in an isolated VM with no persistent storage
|
|
10
|
+
- Enclave fetches credentials using KMS attestation (PCR8(optionally PCR0) values)
|
|
11
|
+
- Credentials never exposed to the host EC2 instance
|
|
12
|
+
|
|
13
|
+
**Single or multi-account:**
|
|
14
|
+
- **Multi-account**: GM runs enclave infrastructure, MM owns KMS/S3 (recommended)
|
|
15
|
+
- **Single account**: Run everything in one AWS account yourself
|
|
16
|
+
|
|
17
|
+
### Data Flow
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
sequenceDiagram
|
|
21
|
+
participant MM as Market Maker
|
|
22
|
+
participant S3 as S3 Bucket
|
|
23
|
+
participant KMS as KMS Key
|
|
24
|
+
participant Enclave as Nitro Enclave
|
|
25
|
+
participant GM as GlassMarkets
|
|
26
|
+
|
|
27
|
+
MM->>S3: 1. Store encrypted credentials
|
|
28
|
+
MM->>KMS: 2. Grant enclave access (PCR8)
|
|
29
|
+
GM->>Enclave: 3. Deploy enclave
|
|
30
|
+
Enclave->>KMS: 4. Request decryption (PCR8 attestation)
|
|
31
|
+
KMS->>Enclave: 5. Return credentials
|
|
32
|
+
Note over Enclave: Credentials isolated from host
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Component Isolation
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
graph TB
|
|
39
|
+
subgraph "Market Maker Account"
|
|
40
|
+
S3[S3 Bucket<br/>Encrypted Credentials]
|
|
41
|
+
KMS[KMS Key<br/>You Control]
|
|
42
|
+
S3 -.->|Encryption| KMS
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
subgraph "GlassMarkets Account"
|
|
46
|
+
EC2[EC2 Instance<br/>Host]
|
|
47
|
+
Enclave[Nitro Enclave<br/>Isolated VM]
|
|
48
|
+
|
|
49
|
+
EC2 -->|Hosts| Enclave
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Enclave -->|PCR8 Attestation| KMS
|
|
53
|
+
KMS -->|Decrypt| Enclave
|
|
54
|
+
Enclave -->|Fetch| S3
|
|
55
|
+
|
|
56
|
+
style Enclave fill:#10B981,stroke:#047857,color:#fff
|
|
57
|
+
style EC2 fill:#F59E0B,stroke:#D97706,color:#fff
|
|
58
|
+
style KMS fill:#3B82F6,stroke:#2563EB,color:#fff
|
|
59
|
+
style S3 fill:#3B82F6,stroke:#2563EB,color:#fff
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Security Model
|
|
63
|
+
|
|
64
|
+
| Layer | Access | Isolation |
|
|
65
|
+
|--------------------|------------------------------|--------------------------------|
|
|
66
|
+
| **Host EC2** | Run/stop enclaves | No credential access |
|
|
67
|
+
| **Nitro Enclave** | KMS decrypt + S3 read | Isolated VM, vsock only |
|
|
68
|
+
| **KMS Key Policy** | Requires PCR8 (signing cert) | Optional PCR0 (proof of build) |
|
|
69
|
+
| **Credentials** | S3 encrypted, versioned | Never exposed to host |
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## Prerequisites
|
|
73
|
+
|
|
74
|
+
- Python 3.12+
|
|
75
|
+
- AWS CLI configured with credentials
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install gm-shield
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
### Set up keyshare infra
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
gm-shield setup
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This creates:
|
|
92
|
+
- An S3 bucket for storing credentials (KMS-encrypted, versioned)
|
|
93
|
+
- A KMS key for encryption (you control it)
|
|
94
|
+
- Config saved to `~/.config/gm-shield/config.yaml`
|
|
95
|
+
|
|
96
|
+
### Deploy enclave infrastructure
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
gm-shield enclave deploy
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This deploys:
|
|
103
|
+
- EC2 instance with Nitro Enclave support
|
|
104
|
+
- IAM role for the enclave
|
|
105
|
+
- Outputs the **Role ARN** attached to the instance profile needed for approval
|
|
106
|
+
|
|
107
|
+
### Get PCR values
|
|
108
|
+
|
|
109
|
+
- From the `https://github.com/glassmarkets/shield-worker/actions/workflows/release.yml` release notes pick up PCR values.
|
|
110
|
+
|
|
111
|
+
- Look for the `PCR8` value in the `Measurements` section.
|
|
112
|
+
- If you want to approve each release build optionally add `PCR0` value.
|
|
113
|
+
|
|
114
|
+
### Approve enclave access
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
gm-shield enclave approve \
|
|
118
|
+
--role-arn <enclave-role-arn> \
|
|
119
|
+
--pcr8 <pcr8-value>
|
|
120
|
+
```
|
|
121
|
+
or interactive
|
|
122
|
+
```bash
|
|
123
|
+
gm-shield enclave approve
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This updates the KMS key policy to allow the enclave (with PCR8 attestation) to decrypt credentials.
|
|
127
|
+
|
|
128
|
+
### Add credentials
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
gm-shield keys create
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Follow the interactive prompts to select an exchange and enter your API credentials. The enclave will fetch and use them securely.
|
|
135
|
+
|
|
136
|
+
## Commands
|
|
137
|
+
|
|
138
|
+
### Market Maker: Credential Storage
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
gm-shield setup # Create S3 bucket, KMS key
|
|
142
|
+
gm-shield config # Display current configuration
|
|
143
|
+
gm-shield teardown # Delete infrastructure
|
|
144
|
+
gm-shield keys list # List all credentials
|
|
145
|
+
gm-shield keys create # Add new credential (interactive)
|
|
146
|
+
gm-shield keys update # Update pairs or labels
|
|
147
|
+
gm-shield keys delete # Delete a credential
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### GlassMarkets: Enclave Infrastructure
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
gm-shield enclave deploy # Deploy Nitro Enclave infrastructure (prompts for labels)
|
|
154
|
+
gm-shield enclave teardown # Delete enclave infrastructure (interactive if multiple)
|
|
155
|
+
gm-shield config # Display configuration (shows all enclaves)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Access Control (Market Maker approves/revokes)
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
gm-shield enclave approve # Grant enclave access (requires PCR8)
|
|
162
|
+
gm-shield enclave revoke # Revoke enclave access
|
|
163
|
+
gm-shield enclave rotate # Rotate PCR attestation values
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
Config is stored at `~/.config/gm-shield/config.yaml`:
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
bucket: my-credentials-bucket
|
|
172
|
+
region: us-east-1
|
|
173
|
+
stack_name: my-keyshare-stack
|
|
174
|
+
stack_id: arn:aws:cloudformation:...
|
|
175
|
+
kms_key_arn: arn:aws:kms:...
|
|
176
|
+
|
|
177
|
+
enclaves:
|
|
178
|
+
- stack_name: gm-enclave-mm1
|
|
179
|
+
stack_id: arn:aws:cloudformation:...
|
|
180
|
+
region: eu-central-1
|
|
181
|
+
role_arn: arn:aws:iam::...:role/...
|
|
182
|
+
instance_profile_arn: arn:aws:iam::...:instance-profile/...
|
|
183
|
+
instance_id: i-xxxxxxxx
|
|
184
|
+
labels:
|
|
185
|
+
- mm1
|
|
186
|
+
- production
|
|
187
|
+
|
|
188
|
+
approved_enclave:
|
|
189
|
+
role_arn: arn:aws:iam::...:role/...
|
|
190
|
+
pcr8: 2d4bd2e9ce136106537f13262b545c459e08ea760dacaef62350cbea418495f3be47926f8f8785ce1b2a653045bcd14c
|
|
191
|
+
pcr0: null
|
|
192
|
+
approved_at: 2026-03-31T21:00:00Z
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## Workflow
|
|
197
|
+
|
|
198
|
+
1. **Run setup**: `gm-shield setup` → creates KMS + S3
|
|
199
|
+
2. **Deploy enclave**: `gm-shield enclave deploy` → deploys enclave stack (prompts for labels)
|
|
200
|
+
3. **Get PCR8**: From GitHub Actions release notes (shield-worker release workflow)
|
|
201
|
+
4. **Approve**: `gm-shield enclave approve --role-arn <...> --pcr8 <...>`
|
|
202
|
+
5. **Add credentials**: `gm-shield keys create`
|
|
203
|
+
|
|
204
|
+
**Multiple enclaves**: Run `gm-shield enclave deploy` multiple times to deploy additional enclaves. Each deployment prompts for labels (e.g., `mm1,production`) to track which MM each enclave serves.
|
|
205
|
+
|
|
206
|
+
## Security Model
|
|
207
|
+
|
|
208
|
+
- **Credentials** are stored in S3 with KMS encryption
|
|
209
|
+
- **Enclave access** is scoped to specific PCR8 (signing certificate) values
|
|
210
|
+
- **Revoke anytime** with `gm-shield enclave revoke`
|
|
211
|
+
- **Isolation**: Credentials decrypted only inside the Nitro Enclave, never exposed to the host
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uv sync
|
|
217
|
+
|
|
218
|
+
uv run pytest
|
|
219
|
+
|
|
220
|
+
uv run pyright
|
|
221
|
+
```
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Market Maker Onboarding
|
|
2
|
+
|
|
3
|
+
Securely share your exchange API credentials with Glass using isolated enclaves.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart LR
|
|
9
|
+
MM[Market Maker]
|
|
10
|
+
S3[Encrypted S3 Storage]
|
|
11
|
+
KMS[KMS Key]
|
|
12
|
+
Enclave[Isolated EC2 Enclave]
|
|
13
|
+
Glass[Glass Team]
|
|
14
|
+
|
|
15
|
+
MM -->|1. Store and configure API keys| S3
|
|
16
|
+
S3 -.->|Encryption| KMS
|
|
17
|
+
Glass -->|2. Deploy| Enclave
|
|
18
|
+
MM -->|3. Approve access| KMS
|
|
19
|
+
Enclave -->|4. Fetch keys| S3
|
|
20
|
+
Enclave -.->|Decrypt| KMS
|
|
21
|
+
|
|
22
|
+
style MM fill:#3B82F6,color:#fff
|
|
23
|
+
style S3 fill:#3B82F6,color:#fff
|
|
24
|
+
style KMS fill:#3B82F6,color:#fff
|
|
25
|
+
style Enclave fill:#10B981,color:#fff
|
|
26
|
+
style Glass fill:#8B5CF6,color:#fff
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Your credentials never leave the isolated enclave**
|
|
30
|
+
|
|
31
|
+
## Security Model
|
|
32
|
+
|
|
33
|
+
| Layer | What You Control | Security Guarantee |
|
|
34
|
+
|----------------|-------------------------|------------------------------------|
|
|
35
|
+
| **Storage** | S3 bucket + KMS key | Credentials encrypted at rest |
|
|
36
|
+
| **Access** | Enclave approval (PCR8) | Only approved enclaves can decrypt |
|
|
37
|
+
| **Revocation** | Revoke anytime | Instantly cut off enclave access |
|
|
38
|
+
|
|
39
|
+
**Key point**: Glass deploys and manages enclaves, but you control who can access your keys through KMS policies.
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- Python 3.12+
|
|
44
|
+
- AWS CLI configured with credentials
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install gm-shield
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### Step 1: Set up infrastructure
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
gm-shield setup
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This creates:
|
|
61
|
+
- S3 bucket for encrypted credentials
|
|
62
|
+
- KMS key (you control it)
|
|
63
|
+
- Config saved to `~/.config/gm-shield/config.yaml`
|
|
64
|
+
|
|
65
|
+
**Share with Glass:**
|
|
66
|
+
- Bucket name
|
|
67
|
+
- Region
|
|
68
|
+
|
|
69
|
+
### Step 2: Glass deploys enclave
|
|
70
|
+
|
|
71
|
+
Glass will:
|
|
72
|
+
1. Deploy a Nitro Enclave in their AWS account
|
|
73
|
+
2. Provide you with the enclave's **Role ARN**
|
|
74
|
+
3. Provide you with the enclave's outbound **IP address** (for whitelisting)
|
|
75
|
+
|
|
76
|
+
### Step 3: Add your exchange credentials
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
gm-shield keys create
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Interactive prompts for exchange and API credentials.
|
|
83
|
+
|
|
84
|
+
The enclave can now fetch and use your credentials securely.
|
|
85
|
+
|
|
86
|
+
**Note:** Remember to add the outbound IP address to your exchange API allowlist!
|
|
87
|
+
|
|
88
|
+
### Step 4: Approve enclave access
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
gm-shield enclave approve
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Enter the enclave Role ARN (from Glass)
|
|
95
|
+
- Enter the enclave PCR8 value: `2d4bd2e9ce136106537f13262b545c459e08ea760dacaef62350cbea418495f3be47926f8f8785ce1b2a653045bcd14c`
|
|
96
|
+
- **Optional:**
|
|
97
|
+
- You can verify all PCRs in the https://github.com/glassmarkets/shield-worker/actions/workflows/release.yml release notes under the `Measurements` section. Optionally add `PCR0` if you want to approve each release build.
|
|
98
|
+
|
|
99
|
+
This updates your KMS policy to allow the enclave to decrypt credentials and S3 policy to allow the enclave to read from S3.
|
|
100
|
+
|
|
101
|
+
## Daily Operations
|
|
102
|
+
|
|
103
|
+
### View credentials
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
gm-shield keys list
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Update credential
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
gm-shield keys update <key-name>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Delete credential
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
gm-shield keys delete <key-name>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### View configuration
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
gm-shield config
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Shows:
|
|
128
|
+
- S3 bucket and KMS key
|
|
129
|
+
- Deployed enclaves (with labels)
|
|
130
|
+
- Approved enclave attestation values
|
|
131
|
+
|
|
132
|
+
## Access Control
|
|
133
|
+
|
|
134
|
+
### Approve additional enclaves
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
gm-shield enclave approve
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Revoke all access
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
gm-shield enclave revoke
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Rotate PCR0
|
|
147
|
+
```bash
|
|
148
|
+
gm-shield enclave rotate
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Immediately removes all enclave access to your credentials.
|
|
152
|
+
|
|
153
|
+
### Delete all infrastructure
|
|
154
|
+
- You can delete all resources, revoking access to all keys using the following command
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
gm-shield teardown
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Troubleshooting
|
|
161
|
+
|
|
162
|
+
### "No approved enclave"
|
|
163
|
+
|
|
164
|
+
Run `gm-shield enclave approve` with the Role ARN and PCR8 from Glass.
|
|
165
|
+
|
|
166
|
+
### "Enclave already approved"
|
|
167
|
+
|
|
168
|
+
Run `gm-shield enclave revoke` first, then approve with new values.
|
|
169
|
+
|
|
170
|
+
### View config
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
gm-shield config
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Check the "Approved Enclave" section for current PCR8 and Role ARN.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gm-shield"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "CLI for market makers to securely share exchange API credentials"
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"click>=8.1.0",
|
|
8
|
+
"boto3>=1.42.80",
|
|
9
|
+
"botocore[crt]>=1.42.80",
|
|
10
|
+
"pyyaml>=6.0",
|
|
11
|
+
"rich>=14.3.3",
|
|
12
|
+
"questionary>=2.0.0",
|
|
13
|
+
"boto3-stubs>=1.42.40",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"boto3-stubs[cloudformation,kms,s3]>=1.42.78",
|
|
19
|
+
"pyright>=1.1.408",
|
|
20
|
+
"pytest>=9.0.2",
|
|
21
|
+
"pytest-github-actions-annotate-failures>=0.4.0",
|
|
22
|
+
"ruff>=0.15.8",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
gm-shield = "exchange_keyshare.cli:main"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/exchange_keyshare"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
pythonpath = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
line-length = 100
|
|
41
|
+
target-version = "py312"
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint]
|
|
44
|
+
select = ["E", "F", "I", "UP"]
|
|
45
|
+
ignore = ["E501"]
|
|
46
|
+
|
|
47
|
+
[tool.pyright]
|
|
48
|
+
pythonVersion = "3.12"
|
|
49
|
+
typeCheckingMode = "strict"
|
|
50
|
+
reportUnnecessaryCast = false # False positive with boto3-stubs
|
|
51
|
+
reportUnknownVariableType = "none" # boto3.client() returns dynamic type
|
|
52
|
+
reportUnknownMemberType = "none" # boto3 client methods
|
|
53
|
+
reportUnknownArgumentType = "none" # boto3 method arguments
|
|
54
|
+
reportUnknownLambdaType = "none" # boto3 callbacks
|
|
55
|
+
reportTypedDictNotRequiredAccess = "none" # boto3 error responses
|