fujin-secrets-1password 0.16.0__py3-none-any.whl
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.
- fujin_secrets_1password/__init__.py +95 -0
- fujin_secrets_1password/py.typed +0 -0
- fujin_secrets_1password-0.16.0.dist-info/METADATA +75 -0
- fujin_secrets_1password-0.16.0.dist-info/RECORD +6 -0
- fujin_secrets_1password-0.16.0.dist-info/WHEEL +4 -0
- fujin_secrets_1password-0.16.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""1Password secret adapter for Fujin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
|
+
from contextlib import closing
|
|
8
|
+
from io import StringIO
|
|
9
|
+
|
|
10
|
+
from dotenv import dotenv_values
|
|
11
|
+
from fujin.config import SecretConfig
|
|
12
|
+
from fujin.errors import SecretResolutionError
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def onepassword(env_content: str, secret_config: SecretConfig) -> str:
|
|
18
|
+
"""1Password secret adapter.
|
|
19
|
+
|
|
20
|
+
Retrieves secrets from 1Password using the `op` CLI.
|
|
21
|
+
Handles concurrent resolution of multiple secrets for performance.
|
|
22
|
+
|
|
23
|
+
Requires user to be pre-authenticated with `op` CLI.
|
|
24
|
+
|
|
25
|
+
Configuration:
|
|
26
|
+
adapter = "1password"
|
|
27
|
+
|
|
28
|
+
Usage pattern in env file:
|
|
29
|
+
SECRET_KEY=$op://personal/aws-key/password
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
env_content: Raw environment file content
|
|
33
|
+
secret_config: Secret configuration (unused for 1password)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Resolved environment content with secrets replaced
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
SecretResolutionError: If secret not found or CLI fails
|
|
40
|
+
"""
|
|
41
|
+
# Parse env file
|
|
42
|
+
with closing(StringIO(env_content)) as buffer:
|
|
43
|
+
env_dict = dotenv_values(stream=buffer)
|
|
44
|
+
|
|
45
|
+
# Identify secrets (values starting with $)
|
|
46
|
+
secrets = {
|
|
47
|
+
key: value for key, value in env_dict.items() if value and value.startswith("$")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if not secrets:
|
|
51
|
+
return env_content
|
|
52
|
+
|
|
53
|
+
# Resolve secrets concurrently
|
|
54
|
+
resolved_secrets = {}
|
|
55
|
+
with ThreadPoolExecutor() as executor:
|
|
56
|
+
future_to_key = {
|
|
57
|
+
executor.submit(_read_secret, secret[1:]): key
|
|
58
|
+
for key, secret in secrets.items()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for future in as_completed(future_to_key):
|
|
62
|
+
key = future_to_key[future]
|
|
63
|
+
try:
|
|
64
|
+
resolved_secrets[key] = future.result()
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise SecretResolutionError(
|
|
67
|
+
f"Failed to retrieve secret for {key}: {e}",
|
|
68
|
+
adapter="1password",
|
|
69
|
+
key=key,
|
|
70
|
+
) from e
|
|
71
|
+
|
|
72
|
+
# Merge resolved secrets back into env dict
|
|
73
|
+
env_dict.update(resolved_secrets)
|
|
74
|
+
|
|
75
|
+
return "\n".join(f'{key}="{value}"' for key, value in env_dict.items())
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _read_secret(name: str) -> str:
|
|
79
|
+
"""Read a single secret from 1Password.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
name: Secret reference (e.g., "op://personal/aws-key/password")
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Secret value
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
SecretResolutionError: If secret not found
|
|
89
|
+
"""
|
|
90
|
+
result = subprocess.run(["op", "read", name], capture_output=True, text=True)
|
|
91
|
+
|
|
92
|
+
if result.returncode != 0:
|
|
93
|
+
raise SecretResolutionError(result.stderr, adapter="1password", key=name)
|
|
94
|
+
|
|
95
|
+
return result.stdout.strip()
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fujin-secrets-1password
|
|
3
|
+
Version: 0.16.0
|
|
4
|
+
Summary: 1Password secret adapter for Fujin
|
|
5
|
+
Author: Tobi
|
|
6
|
+
Author-email: Tobi <tobidegnon@proton.me>
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: fujin-cli>=0.16
|
|
14
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Project-URL: Homepage, https://github.com/Tobi-De/fujin
|
|
17
|
+
Project-URL: Issues, https://github.com/Tobi-De/fujin/issues
|
|
18
|
+
Project-URL: Source, https://github.com/Tobi-De/fujin
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Fujin Secrets - 1Password
|
|
22
|
+
|
|
23
|
+
1Password secret adapter for Fujin deployment tool.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install fujin-secrets-1password
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or with uv:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv pip install fujin-secrets-1password
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Prerequisites
|
|
38
|
+
|
|
39
|
+
Download and install the [1Password CLI](https://developer.1password.com/docs/cli/) and sign in to your account.
|
|
40
|
+
|
|
41
|
+
You need to be actively signed in for Fujin to work with 1Password.
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
Add the following to your `fujin.toml` file:
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
[secrets]
|
|
49
|
+
adapter = "1password"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
In your environment file (`.env` or configured via `envfile` in `fujin.toml`), use 1Password secret references:
|
|
55
|
+
|
|
56
|
+
```env
|
|
57
|
+
DEBUG=False
|
|
58
|
+
AWS_ACCESS_KEY_ID=$op://personal/aws-access-key-id/password
|
|
59
|
+
AWS_SECRET_ACCESS_KEY=$op://personal/aws-secret-access-key/password
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The secret reference format is: `$op://vault/item/field`
|
|
63
|
+
|
|
64
|
+
## How it Works
|
|
65
|
+
|
|
66
|
+
The adapter:
|
|
67
|
+
1. Uses the existing 1Password CLI session (requires you to be signed in)
|
|
68
|
+
2. Resolves all secrets concurrently using `op read <reference>`
|
|
69
|
+
3. Returns the resolved environment variables
|
|
70
|
+
|
|
71
|
+
## Related
|
|
72
|
+
|
|
73
|
+
- [Fujin Documentation](https://github.com/Tobi-De/fujin)
|
|
74
|
+
- [1Password CLI Documentation](https://developer.1password.com/docs/cli/)
|
|
75
|
+
- [1Password Secret References](https://developer.1password.com/docs/cli/secret-references/)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
fujin_secrets_1password/__init__.py,sha256=E8py2UCRsZ86qZS2IzmsHEDu4xAJ7keXwdSpPZcWbyY,2716
|
|
2
|
+
fujin_secrets_1password/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
fujin_secrets_1password-0.16.0.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
|
|
4
|
+
fujin_secrets_1password-0.16.0.dist-info/entry_points.txt,sha256=dI0TfmWiIt2HEZw_CFWTrC8hxunE2sHkSwmd9HhEgcM,65
|
|
5
|
+
fujin_secrets_1password-0.16.0.dist-info/METADATA,sha256=czGdxi-yAMKOCqvsmyP6ozEJsT1r1lKCHnikSHHHJ-0,2091
|
|
6
|
+
fujin_secrets_1password-0.16.0.dist-info/RECORD,,
|