getsops/sops · error

failed to construct Azure Key Vault client to decrypt data

Error message

failed to construct Azure Key Vault client to decrypt data: %w

What it means

Raised in DecryptContext when azkeys.NewClient fails to construct the Azure Key Vault client, typically because the VaultURL is not a valid URL. Client construction happens after credential acquisition and base64 decoding, so this points at the vault URL configuration rather than auth.

Solutions

  1. Set the full vault URL in .sops.yaml, e.g. vault_url: 'https://<vault-name>.vault.azure.net/'
  2. Check that the config key field is not empty or mis-indented so it parses into MasterKey.VaultURL
  3. Fix templating/env substitution that left the vault URL blank

Example fix

// before
azure_kv:
  - vault_url: myvault
// after
azure_kv:
  - vault_url: 'https://myvault.vault.azure.net/'
    key: mykey
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(key.VaultURL)
valid := err == nil && u.Scheme == "https" && strings.HasSuffix(u.Hostname(), ".vault.azure.net")
if !valid { return fmt.Errorf("invalid vault_url %q; use https://<name>.vault.azure.net/", key.VaultURL) }

Try / catch

if err := masterKey.Decrypt(); err != nil {
	if strings.Contains(err.Error(), "failed to construct Azure Key Vault client") {
		return fmt.Errorf("check vault_url in .sops.yaml: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling Decrypt on an Azure KV MasterKey whose VaultURL field is empty, malformed (missing scheme, stray characters), or otherwise rejected by azkeys.NewClient.

Common situations: Missing vault_url in the .sops.yaml Azure KV key entry, hand-written vault URL like 'myvault' instead of 'https://myvault.vault.azure.net/', templated config where the vault URL variable resolved empty.

Related errors


AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01). Data as JSON: /api/errors/1cf54fca806665f1. Report an issue: GitHub.

Appendix: source

Thrown at azkv/keysource.go:278

// DecryptContext decrypts the EncryptedKey field with Azure Key Vault and returns
// the result.
func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) {
	token, err := key.getTokenCredential()
	if err != nil {
		log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Decryption failed")
		return nil, fmt.Errorf("failed to get Azure token credential to decrypt: %w", err)
	}

	rawEncryptedKey, err := base64.RawURLEncoding.DecodeString(key.EncryptedKey)
	if err != nil {
		log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Decryption failed")
		return nil, fmt.Errorf("failed to base64 decode Azure Key Vault encrypted key: %w", err)
	}

	c, err := azkeys.NewClient(key.VaultURL, token, key.clientOptions)
	if err != nil {
		log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Decryption failed")
		return nil, fmt.Errorf("failed to construct Azure Key Vault client to decrypt data: %w", err)
	}

	resp, err := c.Decrypt(ctx, key.Name, key.Version, azkeys.KeyOperationParameters{
		Algorithm: to.Ptr(azkeys.EncryptionAlgorithmRSAOAEP256),
		Value:     rawEncryptedKey,
	}, nil)
	if err != nil {
		log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Decryption failed")
		return nil, fmt.Errorf("failed to decrypt sops data key with Azure Key Vault key '%s': %w", key.ToString(), err)
	}
	log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Decryption succeeded")
	return resp.KeyOperationResult.Result, nil
}

// NeedsRotation returns whether the data key needs to be rotated or not.
func (key *MasterKey) NeedsRotation() bool {
	return time.Since(key.CreationDate) > (azkvTTL)
}

View on GitHub (pinned to 13442bb981)