exchange-keyshare 0.1.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.
- exchange_keyshare-0.1.0/.claude/settings.local.json +12 -0
- exchange_keyshare-0.1.0/.github/workflows/ci.yaml +28 -0
- exchange_keyshare-0.1.0/.github/workflows/publish.yml +20 -0
- exchange_keyshare-0.1.0/.gitignore +39 -0
- exchange_keyshare-0.1.0/CLAUDE.md +122 -0
- exchange_keyshare-0.1.0/PKG-INFO +10 -0
- exchange_keyshare-0.1.0/README.md +62 -0
- exchange_keyshare-0.1.0/pyproject.toml +45 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/__init__.py +3 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/cfn.py +14 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/cli.py +37 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/commands/__init__.py +1 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/commands/keys.py +476 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/commands/setup.py +170 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/config.py +86 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/keys.py +106 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/schema.py +117 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/setup.py +263 -0
- exchange_keyshare-0.1.0/src/exchange_keyshare/templates/stack.yaml +221 -0
- exchange_keyshare-0.1.0/templates +1 -0
- exchange_keyshare-0.1.0/tests/__init__.py +1 -0
- exchange_keyshare-0.1.0/tests/test_cfn.py +20 -0
- exchange_keyshare-0.1.0/tests/test_config.py +71 -0
- exchange_keyshare-0.1.0/tests/test_keys.py +45 -0
- exchange_keyshare-0.1.0/tests/test_schema.py +169 -0
- exchange_keyshare-0.1.0/tests/test_setup.py +14 -0
- exchange_keyshare-0.1.0/uv.lock +407 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(uv run:*)",
|
|
5
|
+
"Bash(git add:*)",
|
|
6
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nFix S3 upload AccessDenied by specifying KMS encryption\n\nThe bucket policy requires explicit KMS encryption on uploads, but\nupload_credential wasn''t passing ServerSideEncryption or SSEKMSKeyId\nparameters. Also, kms_key_arn was retrieved from CloudFormation but\nnever saved to the config file.\n\n- Add kms_key_arn field to Config class\n- Save kms_key_arn during setup\n- Pass kms_key_arn to all upload_credential calls\n- Remove outdated placeholder ARN test\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
7
|
+
"Bash(git push)",
|
|
8
|
+
"Bash(uv sync:*)",
|
|
9
|
+
"Bash(git commit:*)"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
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
|
|
23
|
+
|
|
24
|
+
- name: Run type checker
|
|
25
|
+
run: uv run pyright
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: uv run pytest
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- name: Install uv
|
|
14
|
+
uses: astral-sh/setup-uv@v5
|
|
15
|
+
|
|
16
|
+
- name: Build package
|
|
17
|
+
run: uv build
|
|
18
|
+
|
|
19
|
+
- name: Publish to TestPyPI
|
|
20
|
+
run: uv publish --publish-url https://test.pypi.org/legacy/ --token ${{ secrets.TEST_PYPI_TOKEN }}
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# CLAUDE.md - exchange-keyshare
|
|
2
|
+
|
|
3
|
+
CLI tool for market makers to securely share exchange API credentials.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- Python 3.12+
|
|
8
|
+
- Click (CLI framework)
|
|
9
|
+
- boto3 (AWS SDK)
|
|
10
|
+
- rich (terminal UI)
|
|
11
|
+
- questionary (interactive prompts)
|
|
12
|
+
- uv (package manager)
|
|
13
|
+
- pyright strict mode
|
|
14
|
+
|
|
15
|
+
## Project Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/exchange_keyshare/
|
|
19
|
+
├── cli.py # CLI entrypoint
|
|
20
|
+
├── config.py # Config file handling (~/.config/exchange-keyshare/)
|
|
21
|
+
├── setup.py # Stack creation logic
|
|
22
|
+
├── keys.py # S3 credential operations
|
|
23
|
+
├── schema.py # Credential validation
|
|
24
|
+
├── cfn.py # CloudFormation template loading
|
|
25
|
+
├── templates/
|
|
26
|
+
│ └── stack.yaml # CloudFormation template
|
|
27
|
+
└── commands/
|
|
28
|
+
├── setup.py # `exchange-keyshare setup` command
|
|
29
|
+
└── keys.py # `exchange-keyshare keys` subcommands
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
exchange-keyshare setup # Create AWS infrastructure
|
|
36
|
+
exchange-keyshare keys list # List credentials
|
|
37
|
+
exchange-keyshare keys create # Create credential (interactive)
|
|
38
|
+
exchange-keyshare keys delete # Delete credential
|
|
39
|
+
exchange-keyshare keys update # Update pairs/labels
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Config file: `~/.config/exchange-keyshare/config.yaml`
|
|
45
|
+
|
|
46
|
+
Created by `setup` command with:
|
|
47
|
+
- `bucket`: S3 bucket name
|
|
48
|
+
- `region`: AWS region
|
|
49
|
+
- `stack_name`: CloudFormation stack name
|
|
50
|
+
- `role_arn`: IAM role ARN
|
|
51
|
+
- `external_id`: External ID for role assumption
|
|
52
|
+
|
|
53
|
+
## Environment Variables
|
|
54
|
+
|
|
55
|
+
| Variable | Description |
|
|
56
|
+
|----------|-------------|
|
|
57
|
+
| `EXCHANGE_KEYSHARE_CONFIG` | Override config file path |
|
|
58
|
+
| `AWS_REGION` / `AWS_DEFAULT_REGION` | Default AWS region |
|
|
59
|
+
|
|
60
|
+
## CloudFormation Template
|
|
61
|
+
|
|
62
|
+
The `setup` command deploys `templates/stack.yaml` which creates:
|
|
63
|
+
|
|
64
|
+
**Resources:**
|
|
65
|
+
- `CredentialsBucket` - S3 bucket for credentials (versioned, KMS-encrypted)
|
|
66
|
+
- `AccessLogsBucket` - S3 access logs (90-day retention)
|
|
67
|
+
- `CredentialsKey` - KMS key for encryption (auto-rotating)
|
|
68
|
+
- `CredentialsKeyAlias` - KMS key alias
|
|
69
|
+
- `ConsumerAccessRole` - IAM role for credential consumer to assume (read-only)
|
|
70
|
+
- `CredentialsBucketPolicy` - Enforces KMS encryption on uploads
|
|
71
|
+
- `AccessLogsBucketPolicy` - Allows S3 logging service
|
|
72
|
+
|
|
73
|
+
**Security hardening:**
|
|
74
|
+
- `DeletionPolicy: Retain` on bucket and KMS key (prevents accidental data loss)
|
|
75
|
+
- Bucket policy denies unencrypted uploads or wrong KMS key
|
|
76
|
+
- External ID required for role assumption (confused deputy protection)
|
|
77
|
+
- All public access blocked
|
|
78
|
+
- S3 access logging enabled
|
|
79
|
+
|
|
80
|
+
**Consumer role permissions (read-only):**
|
|
81
|
+
- `s3:ListBucket` (only `exchange-credentials/*` prefix)
|
|
82
|
+
- `s3:GetObject` (only `exchange-credentials/*`)
|
|
83
|
+
- `kms:Decrypt`, `kms:DescribeKey`
|
|
84
|
+
|
|
85
|
+
## Credential Schema
|
|
86
|
+
|
|
87
|
+
Credentials stored as YAML in S3 under `exchange-credentials/` prefix:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
version: "1"
|
|
91
|
+
exchange: binance # binance, coinbase, kraken, kucoin, bitget
|
|
92
|
+
credential:
|
|
93
|
+
api_key: "..."
|
|
94
|
+
api_secret: "..."
|
|
95
|
+
passphrase: "..." # Required for coinbase, kucoin, bitget
|
|
96
|
+
pairs: # Optional, BASE/QUOTE format
|
|
97
|
+
- BTC/USDT
|
|
98
|
+
- ETH/USDT
|
|
99
|
+
labels: # Optional
|
|
100
|
+
- key: environment
|
|
101
|
+
value: production
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Install dependencies
|
|
108
|
+
uv sync
|
|
109
|
+
|
|
110
|
+
# Run tests
|
|
111
|
+
uv run pytest
|
|
112
|
+
|
|
113
|
+
# Type checking
|
|
114
|
+
uv run pyright
|
|
115
|
+
|
|
116
|
+
# Linting
|
|
117
|
+
uv run ruff check src/
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Worktrees
|
|
121
|
+
|
|
122
|
+
Use `.worktrees/` directory for feature branches (already in `.gitignore`).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: exchange-keyshare
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for market makers to securely share exchange API credentials
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: boto3>=1.34.0
|
|
7
|
+
Requires-Dist: click>=8.1.0
|
|
8
|
+
Requires-Dist: pyyaml>=6.0
|
|
9
|
+
Requires-Dist: questionary>=2.0.0
|
|
10
|
+
Requires-Dist: rich>=13.0.0
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# exchange-keyshare
|
|
2
|
+
|
|
3
|
+
CLI tool for market makers to securely share exchange API credentials.
|
|
4
|
+
|
|
5
|
+
## Before You Start
|
|
6
|
+
|
|
7
|
+
The credential consumer will provide you with two values:
|
|
8
|
+
- **Principal ARN**
|
|
9
|
+
- **External ID**
|
|
10
|
+
|
|
11
|
+
Keep these ready for the setup process.
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- Python 3.12+
|
|
16
|
+
- AWS CLI configured with credentials that can create CloudFormation stacks
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install exchange-keyshare
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### 1. Set up infrastructure
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
exchange-keyshare setup
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This creates:
|
|
33
|
+
- An S3 bucket for storing credentials (KMS-encrypted, versioned)
|
|
34
|
+
- A KMS key for encryption (you control it)
|
|
35
|
+
- An IAM role for the credential consumer to assume (read-only access)
|
|
36
|
+
|
|
37
|
+
After setup, share the displayed Role ARN and Bucket with the credential consumer.
|
|
38
|
+
|
|
39
|
+
### 2. Add credentials
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
exchange-keyshare keys create
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Follow the interactive prompts to select an exchange and enter your API credentials.
|
|
46
|
+
|
|
47
|
+
### 3. Manage credentials
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# List all credentials
|
|
51
|
+
exchange-keyshare keys list
|
|
52
|
+
|
|
53
|
+
# Update pairs or labels
|
|
54
|
+
exchange-keyshare keys update <key>
|
|
55
|
+
|
|
56
|
+
# Delete a credential
|
|
57
|
+
exchange-keyshare keys delete <key>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
Config is stored at `~/.config/exchange-keyshare/config.yaml` after running setup.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "exchange-keyshare"
|
|
3
|
+
version = "0.1.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.34.0",
|
|
9
|
+
"pyyaml>=6.0",
|
|
10
|
+
"rich>=13.0.0",
|
|
11
|
+
"questionary>=2.0.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
exchange-keyshare = "exchange_keyshare.cli:main"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["hatchling"]
|
|
19
|
+
build-backend = "hatchling.build"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/exchange_keyshare"]
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
testpaths = ["tests"]
|
|
26
|
+
pythonpath = ["src"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
line-length = 100
|
|
30
|
+
target-version = "py312"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = ["E", "F", "I", "UP"]
|
|
34
|
+
|
|
35
|
+
[tool.pyright]
|
|
36
|
+
pythonVersion = "3.12"
|
|
37
|
+
typeCheckingMode = "strict"
|
|
38
|
+
reportUnnecessaryCast = false # False positive with boto3-stubs
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"boto3-stubs[cloudformation,s3]>=1.34.0",
|
|
43
|
+
"pyright>=1.1.408",
|
|
44
|
+
"pytest>=9.0.2",
|
|
45
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""CloudFormation operations for exchange-keyshare."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_template_path() -> Path:
|
|
7
|
+
"""Get path to CloudFormation template."""
|
|
8
|
+
return Path(__file__).parent / "templates" / "stack.yaml"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_template() -> str:
|
|
12
|
+
"""Load CloudFormation template as string."""
|
|
13
|
+
path = get_template_path()
|
|
14
|
+
return path.read_text()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""CLI entrypoint for exchange-keyshare."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from exchange_keyshare.commands.keys import keys
|
|
8
|
+
from exchange_keyshare.commands.setup import setup
|
|
9
|
+
from exchange_keyshare.config import Config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
@click.version_option()
|
|
14
|
+
@click.option(
|
|
15
|
+
"--config",
|
|
16
|
+
"config_path",
|
|
17
|
+
envvar="EXCHANGE_KEYSHARE_CONFIG",
|
|
18
|
+
type=click.Path(),
|
|
19
|
+
help="Path to config file",
|
|
20
|
+
)
|
|
21
|
+
@click.pass_context
|
|
22
|
+
def main(ctx: click.Context, config_path: str | None) -> None:
|
|
23
|
+
"""Exchange Keyshare - Securely share exchange API credentials."""
|
|
24
|
+
ctx.ensure_object(dict)
|
|
25
|
+
config = Config()
|
|
26
|
+
if config_path:
|
|
27
|
+
config.config_path = Path(config_path)
|
|
28
|
+
config.load()
|
|
29
|
+
ctx.obj["config"] = config
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
main.add_command(setup)
|
|
33
|
+
main.add_command(keys)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command modules."""
|