feat(secrets): add Azure Key Vault provider#5428
Conversation
Add Azure Key Vault as a secret provider for Flipt, following the same patterns as the existing GCP and AWS providers. - Add AzureProviderConfig with vault_url field and validation - Implement provider using Azure SDK (azidentity + azsecrets) - Support emulator mode via AZURE_KEYVAULT_EMULATOR env var - Wire provider into secrets manager and main.go - Add CUE and JSON schema entries - Add unit tests for config, provider, manager, and secret resolution - Add integration test using Lowkey Vault emulator Signed-off-by: Mark Phelps <[email protected]>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v2 #5428 +/- ##
==========================================
+ Coverage 60.36% 60.40% +0.03%
==========================================
Files 140 141 +1
Lines 13906 13995 +89
==========================================
+ Hits 8394 8453 +59
- Misses 4796 4821 +25
- Partials 716 721 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Run go mod tidy to clean up dependencies - Use existing Lowkey Vault image tag (7.1.13) instead of non-existent 2.4.108 Signed-off-by: Mark Phelps <[email protected]>
The Azure SDK validates that the challenge resource URL matches the requested domain, which fails with the Lowkey Vault emulator. Set DisableChallengeResourceVerification to true in emulator mode. Signed-off-by: Mark Phelps <[email protected]>
| Transport: &http.Client{ | ||
| Transport: &http.Transport{ | ||
| TLSClientConfig: &tls.Config{ | ||
| InsecureSkipVerify: true, //nolint:gosec // emulator only |
Check failure
Code scanning / CodeQL
Disabled TLS certificate check High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, the fix is to avoid setting InsecureSkipVerify: true and instead configure TLS so that the emulator’s certificate can be validated. This usually means loading the emulator’s CA certificate into a CertPool and setting RootCAs on the tls.Config, leaving verification enabled.
Concretely for internal/coss/secrets/azure/provider.go, we can keep the special emulator branch, but replace the InsecureSkipVerify: true config with one that loads a CA certificate from a file path specified by an environment variable (e.g., AZURE_KEYVAULT_EMULATOR_CA_CERT). If that variable is not set or the CA file cannot be loaded, we should fail early instead of silently falling back to insecure TLS. This preserves existing functionality (connecting to an emulator with a self‑signed cert) while ensuring proper certificate validation.
To implement this:
- Add imports for
crypto/x509,encoding/pem, andio/ioutil(oros.ReadFilealone; here we’ll useos.ReadFileto avoid an extra import) to parse a PEM CA file. - In the emulator branch (inside
if os.Getenv("AZURE_KEYVAULT_EMULATOR") != "" { ... }), before constructingclientOpts, read the CA path from an env var likeAZURE_KEYVAULT_EMULATOR_CA_CERT. - Read and parse the CA certificate, add it to a new
x509.CertPool, and setTLSClientConfig: &tls.Config{RootCAs: caPool}withoutInsecureSkipVerify. - If the CA env var is missing or parsing fails, return an error from
NewProviderso misconfigurations don’t lead to insecure defaults.
All changes are confined to internal/coss/secrets/azure/provider.go in the shown snippet.
| @@ -8,6 +8,8 @@ | ||
| import ( | ||
| "context" | ||
| "crypto/tls" | ||
| "crypto/x509" | ||
| "encoding/pem" | ||
| "fmt" | ||
| "net/http" | ||
| "os" | ||
| @@ -55,12 +57,37 @@ | ||
|
|
||
| // Use fake credential for emulator testing | ||
| cred = &fakeCredential{} | ||
|
|
||
| // Configure TLS to trust the emulator's CA certificate instead of disabling verification. | ||
| emulatorCACertPath := os.Getenv("AZURE_KEYVAULT_EMULATOR_CA_CERT") | ||
| if emulatorCACertPath == "" { | ||
| return nil, fmt.Errorf("AZURE_KEYVAULT_EMULATOR_CA_CERT must be set when using AZURE_KEYVAULT_EMULATOR") | ||
| } | ||
|
|
||
| caData, err := os.ReadFile(emulatorCACertPath) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("reading emulator CA certificate from %s: %w", emulatorCACertPath, err) | ||
| } | ||
|
|
||
| block, _ := pem.Decode(caData) | ||
| if block == nil { | ||
| return nil, fmt.Errorf("failed to decode PEM for emulator CA certificate at %s", emulatorCACertPath) | ||
| } | ||
|
|
||
| cert, err := x509.ParseCertificate(block.Bytes) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parsing emulator CA certificate at %s: %w", emulatorCACertPath, err) | ||
| } | ||
|
|
||
| caPool := x509.NewCertPool() | ||
| caPool.AddCert(cert) | ||
|
|
||
| clientOpts = &azsecrets.ClientOptions{ | ||
| ClientOptions: azcore.ClientOptions{ | ||
| Transport: &http.Client{ | ||
| Transport: &http.Transport{ | ||
| TLSClientConfig: &tls.Config{ | ||
| InsecureSkipVerify: true, //nolint:gosec // emulator only | ||
| RootCAs: caPool, | ||
| }, | ||
| }, | ||
| }, |
The Lowkey Vault emulator creates vaults at https://<name>.localhost:<port> by default, but the Dagger service binding exposes it as "lowkey-vault". Register a vault alias so the emulator recognizes requests to https://lowkey-vault:8443 as a valid vault URI. Fixes "Unable to find active vault: https://lowkey-vault:8443" error in the signing/azure integration test. Signed-off-by: Mark Phelps <[email protected]>
markphelps
left a comment
There was a problem hiding this comment.
<port> is correct here — it's a Lowkey Vault placeholder that gets substituted at runtime with the actual --server.port value (8443 in this case). I've added a clarifying comment in the code.
Add comment explaining that <port> is a Lowkey Vault runtime placeholder, not a literal value, to avoid confusion during code review. Signed-off-by: Mark Phelps <[email protected]>
Summary
azidentity+azsecrets) withDefaultAzureCredentialfor flexible authentication (managed identity, env vars, Azure CLI, etc.)AZURE_KEYVAULT_EMULATORenv var for integration testing with Lowkey VaultChanges
AzureProviderConfigwithvault_urlfield, validation, defaults, and schema (CUE + JSON)secrets.Providerinterface withGetSecretandListSecretsusing the Azure Key Vault SDKinit(), added manager initialization block, and blank import inmain.gosigning/azuretest case using Lowkey Vault emulator via Dagger