Bitwarden Secrets Manager as a secret provider via an operator-installed bws client
This commit is contained in:
6 files changed
+59
-10
No files matched your search
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
let { class: className = 'w-4 h-4' }: { class?: string } = $props();
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class={className} fill="currentColor">
|
||||
<path d="M39.589,4.608h-16.5H8.214c-0.55,0-0.997,0.444-1,0.994L7.089,24.108 c0,12.064,13.105,18.938,15.686,19.792h0c0,0,0.001,0,0.001,0c0.339,0.111,0.536,0.168,0.536,0.168 c0.09,0.026,0.184,0.039,0.277,0.039h0.5c0.131,0,0.26-0.025,0.38-0.075c0.658-0.271,16.12-6.781,16.12-19.925v-18.5 C40.589,5.056,40.142,4.608,39.589,4.608z M38.589,24.108c0,10.724-12.173,16.836-14.5,17.906V6.608h14.5V24.108z" />
|
||||
</svg>
|
||||
@@ -4,6 +4,8 @@ import OnePassword from './OnePassword.svelte';
|
||||
import Infisical from './Infisical.svelte';
|
||||
import HashiCorpVault from './HashiCorpVault.svelte';
|
||||
import Doppler from './Doppler.svelte';
|
||||
import Bitwarden from './Bitwarden.svelte';
|
||||
import ProtonPass from './ProtonPass.svelte';
|
||||
|
||||
/** Brand icon for a secret-provider type. Falls back to a generic key icon. */
|
||||
export function getProviderIcon(type: string): Component {
|
||||
@@ -17,6 +19,10 @@ export function getProviderIcon(type: string): Component {
|
||||
return HashiCorpVault;
|
||||
case 'doppler':
|
||||
return Doppler;
|
||||
case 'bitwarden':
|
||||
return Bitwarden;
|
||||
case 'proton':
|
||||
return ProtonPass;
|
||||
default:
|
||||
return KeyRound; // any future provider until it has a brand icon
|
||||
}
|
||||
|
||||
@@ -202,11 +202,10 @@ async function runBws(args: string[], limits: CommandLimits): Promise<Buffer> {
|
||||
}
|
||||
return await executeBws(stateDir, args, limits);
|
||||
} finally {
|
||||
try {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
throw new BwsAdapterError('Bitwarden bws temporary state could not be removed');
|
||||
}
|
||||
// Swallow a cleanup failure: throwing here would mask the real error on the
|
||||
// failure path, and turn a successful pull into a thrown error on the happy
|
||||
// path. rm --force rarely fails; a stray temp dir is harmless residue.
|
||||
await rm(stateDir, { recursive: true, force: true }).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { infisicalProvider } from './infisical';
|
||||
import { vaultProvider } from './vault';
|
||||
import { dopplerProvider } from './doppler';
|
||||
import { bitwardenProvider } from './bitwarden';
|
||||
import { protonProvider } from './proton';
|
||||
|
||||
// Registered providers. Adding a new backend means dropping a file in this
|
||||
// directory and registering it here; each implements the SecretProvider
|
||||
@@ -29,7 +30,8 @@ const providers: Record<string, SecretProvider> = {
|
||||
[infisicalProvider.type]: infisicalProvider as SecretProvider,
|
||||
[vaultProvider.type]: vaultProvider as SecretProvider,
|
||||
[dopplerProvider.type]: dopplerProvider as SecretProvider,
|
||||
[bitwardenProvider.type]: bitwardenProvider as SecretProvider
|
||||
[bitwardenProvider.type]: bitwardenProvider as SecretProvider,
|
||||
[protonProvider.type]: protonProvider as SecretProvider
|
||||
};
|
||||
|
||||
/** Returns the provider for a stored type, or undefined if unknown. */
|
||||
|
||||
@@ -32,6 +32,7 @@ export type SecretProviderType =
|
||||
| 'vault'
|
||||
| 'doppler'
|
||||
| 'bitwarden'
|
||||
| 'proton'
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
| (string & {});
|
||||
|
||||
@@ -137,7 +138,12 @@ export interface ConnectConfig {
|
||||
*/
|
||||
export interface InfisicalConfig {
|
||||
host: string;
|
||||
token: string;
|
||||
/** Static service/access token. Supply this OR clientId+clientSecret (Universal Auth). */
|
||||
token?: string;
|
||||
/** Universal Auth (Machine Identity) client ID. Paired with clientSecret. */
|
||||
clientId?: string;
|
||||
/** Universal Auth (Machine Identity) client secret. Paired with clientId. */
|
||||
clientSecret?: string;
|
||||
projectId: string;
|
||||
environment?: string;
|
||||
path?: string;
|
||||
@@ -177,6 +183,15 @@ export interface BitwardenConfig {
|
||||
serverUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proton Pass: a Personal Access Token (`pst_...::...`) authenticates an
|
||||
* operator-installed pass-cli client. Bulk pulls one vault (selected by name);
|
||||
* inline `pass://SHARE_ID/ITEM_ID[/FIELD]` references resolve a single field.
|
||||
*/
|
||||
export interface ProtonConfig {
|
||||
token: string;
|
||||
}
|
||||
|
||||
/** Persisted (encrypted) config, discriminated by the provider `type`. */
|
||||
export type SecretProviderConfig =
|
||||
| ServiceAccountConfig
|
||||
@@ -184,7 +199,8 @@ export type SecretProviderConfig =
|
||||
| InfisicalConfig
|
||||
| VaultConfig
|
||||
| DopplerConfig
|
||||
| BitwardenConfig;
|
||||
| BitwardenConfig
|
||||
| ProtonConfig;
|
||||
|
||||
/**
|
||||
* Config keys that hold a SECRET across every provider type. Only these are stripped
|
||||
@@ -193,7 +209,7 @@ export type SecretProviderConfig =
|
||||
* a non-secret coordinate the user needs to see and edit. Keep in sync with the
|
||||
* `type: 'password'` fields in ProviderModal.svelte's PROVIDER_FIELDS.
|
||||
*/
|
||||
export const SECRET_CONFIG_KEYS = new Set(['token']);
|
||||
export const SECRET_CONFIG_KEYS = new Set(['token', 'clientSecret']);
|
||||
|
||||
/**
|
||||
* Merges an incoming (edit-form) config OVER the stored one for a write/test: the incoming
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
{ value: 'vault', label: 'HashiCorp Vault' },
|
||||
{ value: 'doppler', label: 'Doppler' },
|
||||
{ value: 'bitwarden', label: 'Bitwarden Secrets Manager' },
|
||||
{ value: 'proton', label: 'Proton Pass' },
|
||||
];
|
||||
|
||||
// Config fields per provider type, matching the config shapes in
|
||||
@@ -39,7 +40,9 @@
|
||||
],
|
||||
infisical: [
|
||||
{ key: 'host', label: 'API host', type: 'text', required: true, placeholder: 'https://app.infisical.com', hint: 'Infisical Cloud or your self-hosted URL.' },
|
||||
{ key: 'token', label: 'Access token', type: 'password', required: true, placeholder: 'st...', hint: 'A service token or machine-identity access token.' },
|
||||
{ key: 'token', label: 'Access token', type: 'password', required: false, placeholder: 'st...', hint: 'A static service/access token. Leave blank to use Universal Auth (client ID + secret) below instead.' },
|
||||
{ key: 'clientId', label: 'Universal Auth client ID', type: 'text', required: false, placeholder: 'machine identity client id', hint: 'A Machine Identity client ID. Pair with the client secret; leave blank if using a static token.' },
|
||||
{ key: 'clientSecret', label: 'Universal Auth client secret', type: 'password', required: false, placeholder: 'machine identity client secret', hint: 'The Machine Identity client secret. Exchanged for a short-lived token via Universal Auth.' },
|
||||
{ key: 'projectId', label: 'Project ID', type: 'text', required: true, placeholder: 'workspace / project id', hint: 'The workspace/project the secrets live in.' },
|
||||
{ key: 'environment', label: 'Environment', type: 'text', required: true, placeholder: 'prod', hint: 'Environment slug, e.g. prod / staging.' },
|
||||
{ key: 'path', label: 'Secret path', type: 'text', required: false, placeholder: '/', hint: 'Folder path within the project. Defaults to /.' },
|
||||
@@ -59,6 +62,9 @@
|
||||
{ key: 'token', label: 'Machine Account access token', type: 'password', required: true, placeholder: 'Machine Account access token', hint: 'A Bitwarden Secrets Manager Machine Account token with read access to the Project.' },
|
||||
{ key: 'serverUrl', label: 'Server URL', type: 'text', required: false, placeholder: 'https://vault.bitwarden.com', hint: 'Optional for EU or self-hosted Bitwarden. Leave blank for Bitwarden US cloud.' },
|
||||
],
|
||||
proton: [
|
||||
{ key: 'token', label: 'Personal access token', type: 'password', required: true, placeholder: 'pst_...::...', hint: 'A Proton Pass personal access token (pst_...) used by the operator-installed pass-cli.' },
|
||||
],
|
||||
};
|
||||
|
||||
export function providerTypeLabel(type: string): string {
|
||||
@@ -90,6 +96,11 @@
|
||||
label: 'Project',
|
||||
placeholder: 'Bitwarden Project UUID',
|
||||
hint: 'Bulk-load every secret from this Bitwarden Secrets Manager Project.'
|
||||
},
|
||||
'proton': {
|
||||
label: 'Vault',
|
||||
placeholder: 'Proton Pass vault name',
|
||||
hint: 'Bulk-load every item from this Proton Pass vault. Leave blank to inject only inline pass:// references.'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -415,6 +426,14 @@
|
||||
<code>DOCKHAND_BWS_PATH</code> process override).
|
||||
</p>
|
||||
{/if}
|
||||
{#if formType === 'proton'}
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Proton Pass requires an externally installed or mounted official
|
||||
<code>pass-cli</code> client at <code>/usr/local/bin/pass-cli</code> (or an absolute
|
||||
<code>DOCKHAND_PASS_CLI_PATH</code> process override). Supports both a bulk vault pull
|
||||
and inline <code>pass://</code> references.
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Configuration is stored encrypted.{#if isEditing}
|
||||
Leave secret fields blank to keep the existing values.{/if}
|
||||
|
||||
Reference in new issue
Block a user