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.
Files changed (27) hide show
  1. exchange_keyshare-0.1.0/.claude/settings.local.json +12 -0
  2. exchange_keyshare-0.1.0/.github/workflows/ci.yaml +28 -0
  3. exchange_keyshare-0.1.0/.github/workflows/publish.yml +20 -0
  4. exchange_keyshare-0.1.0/.gitignore +39 -0
  5. exchange_keyshare-0.1.0/CLAUDE.md +122 -0
  6. exchange_keyshare-0.1.0/PKG-INFO +10 -0
  7. exchange_keyshare-0.1.0/README.md +62 -0
  8. exchange_keyshare-0.1.0/pyproject.toml +45 -0
  9. exchange_keyshare-0.1.0/src/exchange_keyshare/__init__.py +3 -0
  10. exchange_keyshare-0.1.0/src/exchange_keyshare/cfn.py +14 -0
  11. exchange_keyshare-0.1.0/src/exchange_keyshare/cli.py +37 -0
  12. exchange_keyshare-0.1.0/src/exchange_keyshare/commands/__init__.py +1 -0
  13. exchange_keyshare-0.1.0/src/exchange_keyshare/commands/keys.py +476 -0
  14. exchange_keyshare-0.1.0/src/exchange_keyshare/commands/setup.py +170 -0
  15. exchange_keyshare-0.1.0/src/exchange_keyshare/config.py +86 -0
  16. exchange_keyshare-0.1.0/src/exchange_keyshare/keys.py +106 -0
  17. exchange_keyshare-0.1.0/src/exchange_keyshare/schema.py +117 -0
  18. exchange_keyshare-0.1.0/src/exchange_keyshare/setup.py +263 -0
  19. exchange_keyshare-0.1.0/src/exchange_keyshare/templates/stack.yaml +221 -0
  20. exchange_keyshare-0.1.0/templates +1 -0
  21. exchange_keyshare-0.1.0/tests/__init__.py +1 -0
  22. exchange_keyshare-0.1.0/tests/test_cfn.py +20 -0
  23. exchange_keyshare-0.1.0/tests/test_config.py +71 -0
  24. exchange_keyshare-0.1.0/tests/test_keys.py +45 -0
  25. exchange_keyshare-0.1.0/tests/test_schema.py +169 -0
  26. exchange_keyshare-0.1.0/tests/test_setup.py +14 -0
  27. 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,3 @@
1
+ """Exchange Keyshare - CLI for market makers to securely share exchange credentials."""
2
+
3
+ __version__ = "0.1.0"
@@ -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."""