273 lines
6.8 KiB
Markdown
273 lines
6.8 KiB
Markdown
# Secrets
|
|
|
|
Le package `secretstore` supporte plusieurs politiques de backend :
|
|
|
|
- `auto` : comportement par défaut, utilise un backend keyring disponible et peut retomber sur l'environnement si `LookupEnv` est fourni
|
|
- `kwallet-only` : impose KWallet et retourne une erreur explicite si KWallet n'est pas disponible
|
|
- `keyring-any` : impose l'utilisation d'un backend keyring disponible
|
|
- `env-only` : lecture seule depuis les variables d'environnement
|
|
- `bitwarden-cli` : utilise le CLI Bitwarden (`bw`) comme backend de vault
|
|
|
|
Backends keyring typiques :
|
|
|
|
- macOS : Keychain
|
|
- Linux : Secret Service ou KWallet selon l'environnement
|
|
- Windows : Credential Manager
|
|
|
|
Ouverture recommandée depuis la policy du manifeste runtime (`mcp.toml` résolu près de l'exécutable) :
|
|
|
|
```go
|
|
store, err := secretstore.OpenFromManifest(secretstore.OpenFromManifestOptions{
|
|
ServiceName: "my-mcp",
|
|
LookupEnv: os.LookupEnv,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
```
|
|
|
|
Exemple bas niveau :
|
|
|
|
```go
|
|
store, err := secretstore.Open(secretstore.Options{
|
|
ServiceName: "my-mcp",
|
|
BackendPolicy: secretstore.BackendAuto,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := store.SetSecret("api-token", "My MCP API token", token); err != nil {
|
|
return err
|
|
}
|
|
|
|
token, err = store.GetSecret("api-token")
|
|
switch {
|
|
case err == nil:
|
|
// secret found
|
|
case errors.Is(err, secretstore.ErrNotFound):
|
|
// first run
|
|
default:
|
|
return err
|
|
}
|
|
```
|
|
|
|
Pour imposer KWallet sur Linux :
|
|
|
|
```go
|
|
store, err := secretstore.Open(secretstore.Options{
|
|
ServiceName: "my-mcp",
|
|
BackendPolicy: secretstore.BackendKWalletOnly,
|
|
})
|
|
```
|
|
|
|
Pour imposer Bitwarden via son CLI :
|
|
|
|
```go
|
|
store, err := secretstore.Open(secretstore.Options{
|
|
ServiceName: "my-mcp",
|
|
BackendPolicy: secretstore.BackendBitwardenCLI,
|
|
// Optionnel si `bw` n'est pas dans le PATH :
|
|
// BitwardenCommand: "/usr/local/bin/bw",
|
|
})
|
|
```
|
|
|
|
## Cache Bitwarden
|
|
|
|
Le backend `bitwarden-cli` met en cache les lectures de secrets par défaut.
|
|
Le cache mémoire évite les appels répétés au CLI dans un même process. Le cache
|
|
disque est chiffré avec une clé dérivée de `BW_SESSION` via HKDF-SHA256 et
|
|
AES-GCM.
|
|
|
|
TTL par défaut : 10 minutes.
|
|
|
|
Pour désactiver le cache dans `mcp.toml` :
|
|
|
|
```toml
|
|
[secret_store]
|
|
backend_policy = "bitwarden-cli"
|
|
bitwarden_cache = false
|
|
```
|
|
|
|
Pour le désactiver sans modifier le manifeste :
|
|
|
|
```bash
|
|
MCP_FRAMEWORK_BITWARDEN_CACHE=0
|
|
```
|
|
|
|
Le fichier de cache et le binaire installé ne suffisent pas à déchiffrer les
|
|
secrets. Si `BW_SESSION` change ou disparaît, les entrées disque existantes
|
|
deviennent inutilisables. Cette protection ne couvre pas un attaquant qui peut
|
|
lire l'environnement ou la mémoire du process pendant l'exécution.
|
|
|
|
Pour vérifier explicitement que Bitwarden est prêt (login + unlock + `BW_SESSION`) :
|
|
|
|
```go
|
|
if err := secretstore.EnsureBitwardenReady(secretstore.Options{
|
|
BitwardenCommand: "bw",
|
|
LookupEnv: os.LookupEnv,
|
|
}); err != nil {
|
|
switch {
|
|
case errors.Is(err, secretstore.ErrBWNotLoggedIn):
|
|
// guider vers `bw login`
|
|
case errors.Is(err, secretstore.ErrBWLocked):
|
|
// guider vers `bw unlock --raw` puis export BW_SESSION
|
|
default:
|
|
// indisponibilité CLI/réseau
|
|
}
|
|
return err
|
|
}
|
|
```
|
|
|
|
Pour lancer un flux interactif `bw login` / `bw unlock --raw`, récupérer `BW_SESSION`
|
|
et le persister localement (fichier `0600` sous le répertoire de config utilisateur) :
|
|
|
|
```go
|
|
session, err := secretstore.LoginBitwarden(secretstore.BitwardenLoginOptions{
|
|
ServiceName: "my-mcp",
|
|
Stdin: os.Stdin,
|
|
Stdout: os.Stdout,
|
|
Stderr: os.Stderr,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("session chargée:", len(session) > 0)
|
|
```
|
|
|
|
Pour réinjecter automatiquement une session persistée dans l'environnement courant :
|
|
|
|
```go
|
|
loaded, err := secretstore.EnsureBitwardenSessionEnv(secretstore.BitwardenSessionOptions{
|
|
ServiceName: "my-mcp",
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("session restaurée depuis disque:", loaded)
|
|
```
|
|
|
|
Pour stocker un secret structuré en JSON :
|
|
|
|
```go
|
|
type Credentials struct {
|
|
Host string `json:"host"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
err = secretstore.SetJSON(store, "imap-credentials", "IMAP credentials", Credentials{
|
|
Host: "imap.example.com",
|
|
Username: "alice",
|
|
Password: token,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
creds, err := secretstore.GetJSON[Credentials](store, "imap-credentials")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_ = creds
|
|
```
|
|
|
|
Pour écrire puis confirmer immédiatement une relecture :
|
|
|
|
```go
|
|
if err := secretstore.SetSecretVerified(store, "api-token", "My MCP API token", token); err != nil {
|
|
return err
|
|
}
|
|
```
|
|
|
|
Pour connaître le backend effectif utilisé :
|
|
|
|
```go
|
|
effective := secretstore.EffectiveBackendPolicy(store)
|
|
fmt.Println("backend effectif:", effective) // bitwarden-cli, env-only, keyring-any...
|
|
```
|
|
|
|
Pour obtenir en un seul appel une description runtime légère (source manifeste,
|
|
policy déclarée/effective, backend affiché) :
|
|
|
|
```go
|
|
desc, err := secretstore.DescribeRuntime(secretstore.DescribeRuntimeOptions{
|
|
ServiceName: "my-mcp",
|
|
LookupEnv: os.LookupEnv,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(secretstore.FormatBackendStatus(desc))
|
|
// declared=... effective=... display=... ready=... source=...
|
|
```
|
|
|
|
`DescribeRuntime` ne contacte pas Bitwarden par défaut. Pour vérifier réellement
|
|
la disponibilité du backend, utiliser le préflight :
|
|
|
|
```go
|
|
report, err := secretstore.PreflightFromManifest(secretstore.PreflightOptions{
|
|
ServiceName: "my-mcp",
|
|
LookupEnv: os.LookupEnv,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(report.Status) // ready | fail
|
|
fmt.Println(report.Summary) // message court
|
|
fmt.Println(report.Remediation) // action recommandée
|
|
```
|
|
|
|
## Debug Bitwarden en 60 secondes
|
|
|
|
Tu peux activer les traces d'appels Bitwarden avec le flag CLI global `--debug`
|
|
(via `bootstrap`) ou en exportant `MCP_FRAMEWORK_BITWARDEN_DEBUG=1`.
|
|
Les commandes `bw` exécutées seront affichées (avec redaction des payloads sensibles).
|
|
|
|
1. Vérifier l'état de session :
|
|
|
|
```bash
|
|
bw status
|
|
```
|
|
|
|
2. Déverrouiller le vault et exporter `BW_SESSION` (ou utiliser `LoginBitwarden`) :
|
|
|
|
- Bash/Zsh :
|
|
|
|
```bash
|
|
export BW_SESSION="$(bw unlock --raw)"
|
|
```
|
|
|
|
- Fish :
|
|
|
|
```fish
|
|
set -x BW_SESSION (bw unlock --raw)
|
|
```
|
|
|
|
- PowerShell :
|
|
|
|
```powershell
|
|
$env:BW_SESSION = (bw unlock --raw)
|
|
```
|
|
|
|
3. Vérifier lecture/écriture rapide :
|
|
|
|
```go
|
|
if err := store.SetSecret("debug-token", "Debug token", "ok"); err != nil {
|
|
return err
|
|
}
|
|
_, err := store.GetSecret("debug-token")
|
|
return err
|
|
```
|
|
|
|
4. Interpréter les erreurs typées :
|
|
|
|
- `secretstore.ErrBWNotLoggedIn` : `bw login` requis.
|
|
- `secretstore.ErrBWLocked` : vault verrouillé ou `BW_SESSION` absent.
|
|
- `secretstore.ErrBWUnavailable` : CLI/réseau indisponible.
|
|
- `secretstore.ErrBackendUnavailable` : policy non satisfiable dans le contexte courant.
|
|
|
|
En mode `env-only`, `GetSecret("API_TOKEN")` lit la variable d'environnement `API_TOKEN`.
|
|
Les opérations d'écriture et de suppression retournent `secretstore.ErrReadOnly`.
|