SDK .NET

Carrega secrets directament en una aplicació .NET en iniciar-se.

.NET SDK

Com carregues secrets d'AWS SSM Parameter Store o d'Azure Key Vault en una aplicació .NET sense fitxer .env? Afegeix el paquet NuGet d'Envilder i crida AddEnvilder a la teva configuració: els secrets que llista envilder.json es resolen a l'inici i es llegeixen com qualsevol altra clau de configuració.

Prerequisits

El paquet apunta a .NET Standard 2.0, així que funciona amb qualsevol versió de .NET amb suport (les proves de l'SDK s'executen a .NET 10, l'LTS actual). Cada proveïdor fa servir la seva cadena de credencials per defecte:

  • AWS: la cadena de credencials per defecte d'AWS (variables d'entorn, fitxer de credencials compartit, SSO o un rol IAM). Defineix "profile" a $config per fer servir un perfil amb nom.
  • Azure: DefaultAzureCredential (az login, identitat gestionada o variables d'entorn).

Política IAM mínima per a AWS SSM

L'SDK només llegeix paràmetres, així que n'hi ha prou amb ssm:GetParameter sobre el teu prefix de ruta. Substitueix la regió, l'ID de compte i el prefix:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "ssm:GetParameter",
    "Resource": "arn:aws:ssm:eu-west-1:123456789012:parameter/myapp/prod/*"
  }]
}

Els valors es llegeixen amb desxifrat. Els paràmetres SecureString xifrats amb la clau aws/ssm per defecte no necessiten res més; si fan servir una clau KMS gestionada per tu, permet també kms:Decrypt sobre aquesta clau.

Rol RBAC mínim per a Azure Key Vault

Per llegir secrets només cal el rol Key Vault Secrets User. Assigna'l a l'àmbit del magatzem, no de la subscripció:

az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee {APP_ID} \
  --scope /subscriptions/{SUB}/resourceGroups/{RG}/providers/Microsoft.KeyVault/vaults/{VAULT}

Si el magatzem encara fa servir polítiques d'accés en lloc de RBAC, concedeix només el permís get de secrets:

az keyvault set-policy \
  --name {VAULT_NAME} \
  --object-id {OBJECT_ID} \
  --secret-permissions get

Instal·lació

dotnet add package Envilder

El fitxer de mapeig

El mateix envilder.json serveix per als exemples següents. Les claus esdevenen claus de configuració o noms de variables d'entorn; els valors són rutes de paràmetres a SSM:

envilder.jsonjson
{
  "$schema": "https://envilder.com/schema/map-file.v1.json",
  "$config": { "provider": "aws" },
  "DB_CONNECTION": "/myapp/prod/db-connection",
  "STRIPE_API_KEY": "/myapp/prod/stripe-key"
}

Per a Key Vault, defineix el proveïdor i la URL del magatzem a $config. Els valors són noms de secrets de Key Vault:

envilder.jsonjson
{
  "$schema": "https://envilder.com/schema/map-file.v1.json",
  "$config": {
    "provider": "azure",
    "vaultUrl": "https://my-vault.vault.azure.net"
  },
  "DB_CONNECTION": "myapp-prod-db-connection",
  "STRIPE_API_KEY": "myapp-prod-stripe-key"
}

L'SDK de .NET envia els noms a Key Vault tal com els escrius, excepte que les barres es converteixen en guions, igual que la CLI: una ruta com /myapp/prod/db-password es llegeix com myapp-prod-db-password. Qualsevol altre caràcter que Key Vault rebutgi (per exemple _ o .) falla amb el seu propi error.

Una línia: resoldre + injectar

Resol secrets del fitxer de mapeig i injecta'ls a Environment en una sola crida:

using Envilder;

// Resolve secrets and inject into Environment
Env.Load("envilder.json");

var dbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD");

Resoldre sense injectar

Obté secrets com un diccionari sense modificar l'entorn:

using Envilder;

var secrets = Env.ResolveFile("envilder.json");
var dbPassword = secrets["DB_PASSWORD"];

Constructor fluent amb sobreescriptures

Sobreescriu la configuració del proveïdor de manera programàtica amb l'API fluent:

using Envilder;

var secrets = Env.FromMapFile("envilder.json")
    .WithProvider(SecretProviderType.Azure)
    .WithVaultUrl("https://my-vault.vault.azure.net")
    .Resolve();

// Or inject directly
Env.FromMapFile("envilder.json")
    .WithProfile("staging")
    .Inject();

Càrrega basada en entorn

Encamina la càrrega de secrets segons el teu entorn actual. Cada entorn mapeja al seu fitxer de secrets:

using Envilder;

var env = Environment.GetEnvironmentVariable("APP_ENV") ?? "development";

Env.Load(env, new Dictionary<string, string?>
{
    ["production"] = "prod-secrets.json",
    ["development"] = "dev-secrets.json",
    ["test"] = null,  // no secrets loaded
});

Validació de secrets

Validació opcional que assegura que tots els secrets resolts tenen valors no buits:

using Envilder;

var secrets = Env.ResolveFile("envilder.json");
secrets.ValidateSecrets(); // throws SecretValidationException if any value is empty

Validació del contracte del fitxer de mapatge

Comprova, sense carregar res i sense fer servir ni mostrar cap valor, que el fitxer de mapatge és vàlid i que cada secret declarat existeix amb les credencials actuals: la mateixa comprovació que envilder validate. Els errors de validació esperats es retornen al resultat en lloc de llançar-se; un fitxer de mapatge inexistent sí que llança un error. Cada clau informa exists, missing, access_denied, authentication_failed o provider_error:

Consulta la referència d'envilder validate per als estats i els permisos que necessita la comprovació →

using Envilder;

var result = await Env.FromMapFile("envilder.json").ValidateContractAsync();
if (!result.IsValid)
{
    foreach (var error in result.MapFileErrors)
    {
        Console.WriteLine($"{error.Key}: {error.Message}");
    }

    foreach (var key in result.Keys.Where(k => k.Status != SecretExistenceStatus.Exists))
    {
        Console.WriteLine($"{key.Variable} ({key.Path}): {key.Status}");
    }
}

Per fer fallar la càrrega, activa la validació del contracte al builder. La comprovació s'executa una vegada abans de resoldre i l'error anomena les claus que fallen, mai els seus valors. Està desactivada per defecte, així que una càrrega normal no fa cap crida extra al proveïdor. A diferència de la validació de secrets, que revisa els valors resolts, revisa el fitxer de mapatge i les claus abans de resoldre:

var secrets = await Env.FromMapFile("envilder.json")
    .WithContractValidation()
    .InjectAsync(); // throws ContractValidationException

Avançat: control programàtic total

Analitza el fitxer de mapeig, resol secrets i injecta'ls a les variables d'entorn:

using Envilder;

var secrets = await Env.ResolveFileAsync("envilder.json");
EnvilderClient.InjectIntoEnvironment(secrets);

ASP.NET Core amb IConfiguration

Crida AddEnvilder just després de CreateBuilder. Les claus es llegeixen tal com s'escriuen al fitxer de mapeig, així que fes servir noms de variable d'entorn: el mateix fitxer també funciona amb la CLI i la resta d'SDKs.

Program.cscsharp
var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEnvilder("envilder.json");
builder.Services.Configure<StripeOptions>(options =>
    options.ApiKey = builder.Configuration["STRIPE_API_KEY"] ?? "");

var app = builder.Build();

app.MapGet("/", (IConfiguration config) => new
{
    dbConfigured = config["DB_CONNECTION"] is not null,
    stripeConfigured = config["STRIPE_API_KEY"] is not null,
});

app.Run();

public class StripeOptions
{
    public string ApiKey { get; set; } = "";
}

En cridar l'endpoint, la resposta és:

{"dbConfigured":true,"stripeConfigured":true}

Els secrets es resolen una sola vegada, mentre es construeix la configuració, amb una petició per cada entrada del fitxer. Els valors no s'actualitzen mentre l'aplicació s'executa: reinicia-la per recollir un secret rotat. Un secret que no existeix s'omet sense cap error, així que comprova les claus sense les quals l'aplicació no pot arrencar.

Injecció de dependències

AddEnvilder sobre IServiceCollection registra un EnvilderClient i el fitxer de mapeig analitzat com a singletons, per al codi que resol secrets sota demanda:

builder.Services.AddEnvilder("envilder.json");

app.MapGet("/count", async (EnvilderClient client, ParsedMapFile map) =>
    (await client.ResolveSecretsAsync(map)).Count);

Worker o aplicació de consola sense DI

No cal cap host. Resol el fitxer amb Env, valida'l i fes servir el diccionari directament:

envilder.jsonjson
{
  "PAYMENTS_API_KEY": "/myapp/prod/payments/api-key",
  "PAYMENTS_WEBHOOK_SECRET": "/myapp/prod/payments/webhook-secret"
}
Program.cscsharp
using Envilder;

try
{
    var secrets = await Env.ResolveFileAsync("envilder.json");

    string[] required = { "PAYMENTS_API_KEY", "PAYMENTS_WEBHOOK_SECRET" };
    var missing = required.Where(key => !secrets.ContainsKey(key)).ToList();
    if (missing.Count > 0)
    {
        Console.Error.WriteLine($"Missing secrets: {string.Join(", ", missing)}");
        return 1;
    }

    secrets.ValidateSecrets(); // rejects empty values
    Console.WriteLine($"Resolved {secrets.Count} secrets");
    return 0;
}
catch (Exception ex) when (ex is SsoSessionExpiredException
                              or ExpiredCredentialsException)
{
    Console.Error.WriteLine(ex.Message);
    return 1;
}

Sortida quan els dos secrets es resolen:

Resolved 2 secrets

En un Worker Service, crida Env.Load abans de crear el host. Els valors passen a ser variables d'entorn del procés, que la configuració del host ja llegeix, així que els guions baixos dobles funcionen com a separadors de secció (Payments__ApiKey es llegeix com Payments:ApiKey).

Program.cscsharp
using Envilder;

Env.Load("envilder.json");

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Build().Run();

Combinació amb appsettings.{Environment}.json

ASP.NET Core aplica les fonts de configuració en ordre i guanya l'última. El host per defecte les afegeix de menys a més prioritat:

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. Secrets d'usuari (només a Development)
  4. Variables d'entorn
  5. Arguments de línia d'ordres
  6. Envilder, quan s'afegeix després de CreateBuilder com a l'exemple anterior

Com que AddEnvilder s'executa després de CreateBuilder, els seus secrets sobreescriuen totes les fonts per defecte, incloent-hi la línia d'ordres. Deixa els valors per defecte no secrets a appsettings i fes que Envilder aporti només secrets. Una clau que s'omet perquè el seu secret no existeix no esborra res: el valor d'appsettings continua vigent.

Perquè les variables d'entorn tornin a guanyar en un desplegament concret, afegeix aquesta font després d'Envilder:

builder.Configuration
    .AddEnvilder("envilder.json")
    .AddEnvironmentVariables();

Per fer servir un fitxer diferent per entorn, construeix-ne el nom a partir del nom de l'entorn:

builder.Configuration.AddEnvilder(
    $"envilder.{builder.Environment.EnvironmentName}.json");

Sense l'SDK: envilder exec

Si l'aplicació només necessita els valors com a variables d'entorn, prescindeix del paquet i deixa que la CLI l'arrenqui. Els secrets es passen al procés, no s'escriu cap fitxer .env i l'ordre no s'inicia si algun secret no es pot resoldre:

envilder exec -- dotnet run

El proveïdor de variables d'entorn de .NET converteix els guions baixos dobles en separadors de secció, així que una clau com ConnectionStrings__Default es llegeix com ConnectionStrings:Default sense cap codi addicional.

Referència d'envilder exec →

Errors habituals

Cada missatge següent és el text exacte que llança l'SDK o que retorna el proveïdor de núvol.

ExpiredCredentialsException

AWS credentials are expired or invalid. Your security token or SSO session may have expired. Refresh your credentials and retry (for SSO, run: aws sso login).

El token de seguretat o la sessió SSO ha caducat. Renova les credencials (aws sso login si fas servir SSO) i torna a executar. L'excepció original d'AWS és a InnerException.

SsoSessionExpiredException

Your AWS SSO session for profile 'dev' has expired. Run 'aws sso login --profile dev' and re-run this command.

Executa l'ordre que indica el missatge. Sense perfil a $config, el missatge és: Your AWS SSO session has expired. Run 'aws sso login' and re-run this command. La propietat ProfileName conté el perfil, o null.

Paràmetre o secret inexistent

No secrets were resolved.
The following secrets have empty or missing values: STRIPE_KEY

Un paràmetre o secret que no existeix s'omet sense error: la clau no és al diccionari ni a IConfiguration. ValidateSecrets només llança l'excepció quan no s'ha resolt res o hi ha un valor buit, així que comprova també les claus de les quals depens (per exemple amb TryGetValue) i verifica la ruta al fitxer.

Permís denegat

User: arn:aws:sts::123456789012:assumed-role/myapp-prod/session is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:eu-west-1:123456789012:parameter/myapp/prod/stripe-key because no identity-based policy allows the ssm:GetParameter action

La identitat no té ssm:GetParameter sobre aquest paràmetre. Compara l'ARN de Resource, regió inclosa, amb la política anterior.

Caller is not authorized to perform action on resource.

Assigna Key Vault Secrets User sobre el magatzem. Les assignacions de rol poden trigar uns minuts a propagar-se.

Secret no trobat a Azure

L'SDK envia cada valor tal qual, excepte que les barres es converteixen en guions: un nom en forma de ruta com /myapp/prod/db-password es llegeix com myapp-prod-db-password, igual que al CLI. Si el secret continua sense aparèixer, comprova que el vault tingui un secret amb aquest nom; els noms que només difereixen en majúscules són el mateix secret.

Errors de configuració

MissatgeSolució
Map file not found.
Map file not found: envilder.json
La ruta és relativa al directori de treball, no a la carpeta del projecte. Copia el fitxer al directori de sortida o passa una ruta absoluta.
Vault URL must be provided for Azure Key Vault provider.
Afegeix "vaultUrl" a $config o passa'l amb EnvilderOptions.
AWS profile cannot be used with Azure Key Vault provider.
Elimina "profile" de $config: només s'aplica a AWS.
Vault URL cannot be used with AWS SSM provider.
Elimina "vaultUrl" o defineix "provider" com a "azure".
AWS profile 'dev' was not found in the credential store.
Comprova el nom a ~/.aws/credentials o ~/.aws/config, o elimina "profile" per fer servir la cadena de credencials per defecte.