fujin-secrets-bitwarden 0.16.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.
- fujin_secrets_bitwarden-0.16.0/PKG-INFO +80 -0
- fujin_secrets_bitwarden-0.16.0/README.md +60 -0
- fujin_secrets_bitwarden-0.16.0/pyproject.toml +33 -0
- fujin_secrets_bitwarden-0.16.0/src/fujin_secrets_bitwarden/__init__.py +165 -0
- fujin_secrets_bitwarden-0.16.0/src/fujin_secrets_bitwarden/py.typed +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fujin-secrets-bitwarden
|
|
3
|
+
Version: 0.16.0
|
|
4
|
+
Summary: Bitwarden 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 - Bitwarden
|
|
22
|
+
|
|
23
|
+
Bitwarden secret adapter for Fujin deployment tool.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install fujin-secrets-bitwarden
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or with uv:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv pip install fujin-secrets-bitwarden
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Prerequisites
|
|
38
|
+
|
|
39
|
+
Download and install the [Bitwarden CLI](https://bitwarden.com/help/cli/#download-and-install) and log in to your account.
|
|
40
|
+
|
|
41
|
+
You should be able to run `bw get password <name_of_secret>` and get the value for the secret. This is the command that will be executed when pulling your secrets.
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
Add the following to your `fujin.toml` file:
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
[secrets]
|
|
49
|
+
adapter = "bitwarden"
|
|
50
|
+
password_env = "BW_PASSWORD"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
To unlock the Bitwarden vault, the password is required. Set the `BW_PASSWORD` environment variable in your shell. When Fujin signs in, it will always sync the vault first.
|
|
54
|
+
|
|
55
|
+
Alternatively, you can set the `BW_SESSION` environment variable. If `BW_SESSION` is present, Fujin will use it directly without signing in or syncing the vault. In this case, the `password_env` configuration is not required.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
In your environment file (`.env` or configured via `envfile` in `fujin.toml`), prefix secret values with `$`:
|
|
60
|
+
|
|
61
|
+
```env
|
|
62
|
+
DEBUG=False
|
|
63
|
+
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
|
|
64
|
+
AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The `$` sign indicates to Fujin that it is a secret that should be resolved using the configured adapter.
|
|
68
|
+
|
|
69
|
+
## How it Works
|
|
70
|
+
|
|
71
|
+
The adapter:
|
|
72
|
+
1. Authenticates with Bitwarden using `BW_SESSION` or unlocks the vault with the configured password
|
|
73
|
+
2. Syncs the vault (when unlocking)
|
|
74
|
+
3. Resolves all secrets concurrently using `bw get password <name> --raw`
|
|
75
|
+
4. Returns the resolved environment variables
|
|
76
|
+
|
|
77
|
+
## Related
|
|
78
|
+
|
|
79
|
+
- [Fujin Documentation](https://github.com/Tobi-De/fujin)
|
|
80
|
+
- [Bitwarden CLI Documentation](https://bitwarden.com/help/cli/)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Fujin Secrets - Bitwarden
|
|
2
|
+
|
|
3
|
+
Bitwarden secret adapter for Fujin deployment tool.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install fujin-secrets-bitwarden
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with uv:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv pip install fujin-secrets-bitwarden
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
Download and install the [Bitwarden CLI](https://bitwarden.com/help/cli/#download-and-install) and log in to your account.
|
|
20
|
+
|
|
21
|
+
You should be able to run `bw get password <name_of_secret>` and get the value for the secret. This is the command that will be executed when pulling your secrets.
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
Add the following to your `fujin.toml` file:
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
[secrets]
|
|
29
|
+
adapter = "bitwarden"
|
|
30
|
+
password_env = "BW_PASSWORD"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
To unlock the Bitwarden vault, the password is required. Set the `BW_PASSWORD` environment variable in your shell. When Fujin signs in, it will always sync the vault first.
|
|
34
|
+
|
|
35
|
+
Alternatively, you can set the `BW_SESSION` environment variable. If `BW_SESSION` is present, Fujin will use it directly without signing in or syncing the vault. In this case, the `password_env` configuration is not required.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
In your environment file (`.env` or configured via `envfile` in `fujin.toml`), prefix secret values with `$`:
|
|
40
|
+
|
|
41
|
+
```env
|
|
42
|
+
DEBUG=False
|
|
43
|
+
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
|
|
44
|
+
AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `$` sign indicates to Fujin that it is a secret that should be resolved using the configured adapter.
|
|
48
|
+
|
|
49
|
+
## How it Works
|
|
50
|
+
|
|
51
|
+
The adapter:
|
|
52
|
+
1. Authenticates with Bitwarden using `BW_SESSION` or unlocks the vault with the configured password
|
|
53
|
+
2. Syncs the vault (when unlocking)
|
|
54
|
+
3. Resolves all secrets concurrently using `bw get password <name> --raw`
|
|
55
|
+
4. Returns the resolved environment variables
|
|
56
|
+
|
|
57
|
+
## Related
|
|
58
|
+
|
|
59
|
+
- [Fujin Documentation](https://github.com/Tobi-De/fujin)
|
|
60
|
+
- [Bitwarden CLI Documentation](https://bitwarden.com/help/cli/)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "uv_build"
|
|
3
|
+
requires = [ "uv-build>=0.9.18,<0.10" ]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fujin-secrets-bitwarden"
|
|
7
|
+
version = "0.16.0"
|
|
8
|
+
description = "Bitwarden secret adapter for Fujin"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Tobi", email = "tobidegnon@proton.me" },
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"fujin-cli>=0.16",
|
|
24
|
+
"python-dotenv>=1.0.1",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
urls.Homepage = "https://github.com/Tobi-De/fujin"
|
|
28
|
+
urls.Issues = "https://github.com/Tobi-De/fujin/issues"
|
|
29
|
+
urls.Source = "https://github.com/Tobi-De/fujin"
|
|
30
|
+
entry-points."fujin.secrets".bitwarden = "fujin_secrets_bitwarden:bitwarden"
|
|
31
|
+
|
|
32
|
+
[tool.uv.sources]
|
|
33
|
+
fujin-cli = { workspace = true }
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Bitwarden secret adapter for Fujin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
8
|
+
from contextlib import closing
|
|
9
|
+
from io import StringIO
|
|
10
|
+
|
|
11
|
+
from dotenv import dotenv_values
|
|
12
|
+
from fujin.config import SecretConfig
|
|
13
|
+
from fujin.errors import SecretResolutionError
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def bitwarden(env_content: str, secret_config: SecretConfig) -> str:
|
|
19
|
+
"""Bitwarden secret adapter.
|
|
20
|
+
|
|
21
|
+
Retrieves secrets from Bitwarden vault using the `bw` CLI.
|
|
22
|
+
Handles concurrent resolution of multiple secrets for performance.
|
|
23
|
+
|
|
24
|
+
Configuration:
|
|
25
|
+
- Requires BW_SESSION environment variable, OR
|
|
26
|
+
- Set password_env to unlock vault using environment variable
|
|
27
|
+
|
|
28
|
+
Example fujin.toml:
|
|
29
|
+
[secrets]
|
|
30
|
+
adapter = "bitwarden"
|
|
31
|
+
password_env = "BW_PASSWORD" # Optional
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
env_content: Raw environment file content
|
|
35
|
+
secret_config: Secret configuration with adapter settings
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Resolved environment content with secrets replaced
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
SecretResolutionError: If authentication fails or secret not found
|
|
42
|
+
"""
|
|
43
|
+
# Parse env file
|
|
44
|
+
with closing(StringIO(env_content)) as buffer:
|
|
45
|
+
env_dict = dotenv_values(stream=buffer)
|
|
46
|
+
|
|
47
|
+
# Identify secrets (values starting with $)
|
|
48
|
+
secrets = {
|
|
49
|
+
key: value for key, value in env_dict.items() if value and value.startswith("$")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if not secrets:
|
|
53
|
+
return env_content
|
|
54
|
+
|
|
55
|
+
# Authenticate with Bitwarden
|
|
56
|
+
session = os.getenv("BW_SESSION")
|
|
57
|
+
if not session:
|
|
58
|
+
if not secret_config.password_env:
|
|
59
|
+
raise SecretResolutionError(
|
|
60
|
+
"You need to set the password_env to use the bitwarden adapter "
|
|
61
|
+
"or set the BW_SESSION environment variable",
|
|
62
|
+
adapter="bitwarden",
|
|
63
|
+
)
|
|
64
|
+
session = _signin(secret_config.password_env)
|
|
65
|
+
|
|
66
|
+
# Resolve secrets concurrently
|
|
67
|
+
resolved_secrets = {}
|
|
68
|
+
with ThreadPoolExecutor() as executor:
|
|
69
|
+
future_to_key = {
|
|
70
|
+
executor.submit(_read_secret, secret[1:], session): key
|
|
71
|
+
for key, secret in secrets.items()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for future in as_completed(future_to_key):
|
|
75
|
+
key = future_to_key[future]
|
|
76
|
+
try:
|
|
77
|
+
resolved_secrets[key] = future.result()
|
|
78
|
+
except Exception as e:
|
|
79
|
+
raise SecretResolutionError(
|
|
80
|
+
f"Failed to retrieve secret for {key}: {e}",
|
|
81
|
+
adapter="bitwarden",
|
|
82
|
+
key=key,
|
|
83
|
+
) from e
|
|
84
|
+
|
|
85
|
+
# Merge resolved secrets back into env dict
|
|
86
|
+
env_dict.update(resolved_secrets)
|
|
87
|
+
|
|
88
|
+
return "\n".join(f'{key}="{value}"' for key, value in env_dict.items())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _read_secret(name: str, session: str) -> str:
|
|
92
|
+
"""Read a single secret from Bitwarden.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
name: Secret name/identifier
|
|
96
|
+
session: Bitwarden session token
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Secret value
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
SecretResolutionError: If secret not found
|
|
103
|
+
"""
|
|
104
|
+
result = subprocess.run(
|
|
105
|
+
[
|
|
106
|
+
"bw",
|
|
107
|
+
"get",
|
|
108
|
+
"password",
|
|
109
|
+
name,
|
|
110
|
+
"--raw",
|
|
111
|
+
"--session",
|
|
112
|
+
session,
|
|
113
|
+
"--nointeraction",
|
|
114
|
+
],
|
|
115
|
+
capture_output=True,
|
|
116
|
+
text=True,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if result.returncode != 0:
|
|
120
|
+
raise SecretResolutionError(
|
|
121
|
+
f"Password not found for {name}", adapter="bitwarden", key=name
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return result.stdout.strip()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _signin(password_env: str) -> str:
|
|
128
|
+
"""Unlock Bitwarden vault and return session token.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
password_env: Name of environment variable containing vault password
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Bitwarden session token
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
SecretResolutionError: If authentication fails
|
|
138
|
+
"""
|
|
139
|
+
# Sync vault first
|
|
140
|
+
sync_result = subprocess.run(["bw", "sync"], capture_output=True, text=True)
|
|
141
|
+
if sync_result.returncode != 0:
|
|
142
|
+
raise SecretResolutionError(
|
|
143
|
+
f"Bitwarden sync failed: {sync_result.stdout}", adapter="bitwarden"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Unlock vault
|
|
147
|
+
unlock_result = subprocess.run(
|
|
148
|
+
[
|
|
149
|
+
"bw",
|
|
150
|
+
"unlock",
|
|
151
|
+
"--nointeraction",
|
|
152
|
+
"--passwordenv",
|
|
153
|
+
password_env,
|
|
154
|
+
"--raw",
|
|
155
|
+
],
|
|
156
|
+
capture_output=True,
|
|
157
|
+
text=True,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if unlock_result.returncode != 0:
|
|
161
|
+
raise SecretResolutionError(
|
|
162
|
+
f"Bitwarden unlock failed: {unlock_result.stderr}", adapter="bitwarden"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return unlock_result.stdout.strip()
|
|
File without changes
|