From 3a123f41e492277e9882d049cdd0e6eadf7c4998 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sun, 19 Jul 2026 21:42:41 +0200 Subject: [PATCH] refactor(oidc): inject Azure Key Vault client factory (closes #1464) Extract the azidentity.NewDefaultAzureCredential + azkeys.NewClient construction out of NewAzureKeyVaultSigner into a package-level newAzureKeyVaultClient hook, mirroring the AzureSPProber.NewClient seam in internal/commitmentopts/probe_azure.go. This lets tests override the hook with a fake and exercise the fully-configured Azure branch of NewSignerFromEnv without spawning the live credential chain (az/pwsh, macOS keychain prompts). Adds TestNewSignerFromEnv_AzureFullyConfigured, which was previously impossible to cover. --- internal/oidc/azure_factory_test.go | 36 +++++++++++++++++++++++++++++ internal/oidc/azure_signer.go | 25 +++++++++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/internal/oidc/azure_factory_test.go b/internal/oidc/azure_factory_test.go index 6a014613c..6fdd50e8e 100644 --- a/internal/oidc/azure_factory_test.go +++ b/internal/oidc/azure_factory_test.go @@ -181,3 +181,39 @@ func TestNewSignerFromEnv_AzureHalfConfigured(t *testing.T) { }) } } + +// --------------------------------------------------------------------------- +// #1464 -- Azure factory branch fully configured (both env vars set) +// --------------------------------------------------------------------------- + +// TestNewSignerFromEnv_AzureFullyConfigured verifies that NewSignerFromEnv +// reaches NewAzureKeyVaultSigner's client-construction path when both Azure +// env vars are set. It overrides the package-level newAzureKeyVaultClient +// factory hook with a fake so the test never spawns the real +// azidentity.NewDefaultAzureCredential chain (az/pwsh, macOS keychain +// prompts) that made this branch untestable before #1464. +func TestNewSignerFromEnv_AzureFullyConfigured(t *testing.T) { + t.Setenv(envSourceCloud, "azure") + t.Setenv(envAWSSigningKeyID, "") + t.Setenv(envGCPKeyResource, "") + + const wantVaultURL = "https://my-vault.vault.azure.net/" + t.Setenv(envAzureVaultURL, wantVaultURL) + t.Setenv(envAzureKeyName, "my-key") + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + var gotVaultURL string + origFactory := newAzureKeyVaultClient + newAzureKeyVaultClient = func(vaultURL string) (AzureKeyVaultClient, error) { + gotVaultURL = vaultURL + return &fakeAzureKeyVaultClient{key: &key.PublicKey}, nil + } + t.Cleanup(func() { newAzureKeyVaultClient = origFactory }) + + signer, err := NewSignerFromEnv(context.Background()) + require.NoError(t, err) + require.NotNil(t, signer) + assert.Equal(t, wantVaultURL, gotVaultURL, "factory must receive the configured vault URL") +} diff --git a/internal/oidc/azure_signer.go b/internal/oidc/azure_signer.go index 4007110f5..8bb87e8b1 100644 --- a/internal/oidc/azure_signer.go +++ b/internal/oidc/azure_signer.go @@ -31,6 +31,23 @@ type AzureKeyVaultSigner struct { once sync.Once } +// newAzureKeyVaultClient builds the real AzureKeyVaultClient using the +// standard azidentity default credential chain. It is a package-level var +// (rather than a call baked into NewAzureKeyVaultSigner) so tests can +// override it with a fake and exercise the fully-configured factory branch +// without spawning the live credential chain (az/pwsh, macOS keychain, etc). +var newAzureKeyVaultClient = func(vaultURL string) (AzureKeyVaultClient, error) { + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, fmt.Errorf("oidc: azure default credential: %w", err) + } + client, err := azkeys.NewClient(vaultURL, cred, nil) + if err != nil { + return nil, fmt.Errorf("oidc: azkeys client: %w", err) + } + return client, nil +} + // NewAzureKeyVaultSigner constructs a signer against a Key Vault using // the standard azidentity default credential chain. vaultURL is the // full vault URL (e.g. https://cudly-vault.vault.azure.net/); keyName @@ -39,13 +56,9 @@ func NewAzureKeyVaultSigner(ctx context.Context, vaultURL, keyName string) (*Azu if vaultURL == "" || keyName == "" { return nil, fmt.Errorf("oidc: azure key vault signer requires vaultURL + keyName") } - cred, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - return nil, fmt.Errorf("oidc: azure default credential: %w", err) - } - client, err := azkeys.NewClient(vaultURL, cred, nil) + client, err := newAzureKeyVaultClient(vaultURL) if err != nil { - return nil, fmt.Errorf("oidc: azkeys client: %w", err) + return nil, err } return NewAzureKeyVaultSignerFromClient(client, keyName, ""), nil }