Skip to content

Latest commit

 

History

History
417 lines (340 loc) · 15.1 KB

README.md

File metadata and controls

417 lines (340 loc) · 15.1 KB

Azure Key Vault Secrets client library for Go

Azure Key Vault helps solve the following problems:

  • Secrets management (this library) - securely store and control access to tokens, passwords, certificates, API keys, and other secrets
  • Cryptographic key management (azkeys) - create, store, and control access to the keys used to encrypt your data
  • Certificate management (azcertificates) - create, manage, and deploy public and private SSL/TLS certificates Azure Key Vault helps securely store and control access to tokens, passwords, certificates, API keys, and other secrets.

Source code | Package (pkg.go.dev) | Product documentation | Samples

Getting started

Install packages

Install azsecrets and azidentity:

go get -u github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets

azidentity is used for Azure Active Directory authentication as demonstrated below.

go get -u github.com/Azure/azure-sdk-for-go/sdk/azidentity

Prerequisites

  • An Azure subscription

  • Go version 1.16 or later

  • A Key Vault. If you need to create one, you can use the Azure Cloud Shell to create one with these commands (replace "my-resource-group" and "my-key-vault" with your own, unique names):

    (Optional) if you want a new resource group to hold the Key Vault:

    az group create --name my-resource-group --location westus2

    Create the Key Vault:

    az keyvault create --resource-group my-resource-group --name my-key-vault

    Output:

    {
        "id": "...",
        "location": "westus2",
        "name": "my-key-vault",
        "properties": {
            "accessPolicies": [...],
            "createMode": null,
            "enablePurgeProtection": null,
            "enableSoftDelete": null,
            "enabledForDeployment": false,
            "enabledForDiskEncryption": null,
            "enabledForTemplateDeployment": null,
            "networkAcls": null,
            "provisioningState": "Succeeded",
            "sku": { "name": "standard" },
            "tenantId": "...",
            "vaultUri": "https://my-key-vault.vault.azure.net/"
        },
        "resourceGroup": "my-resource-group",
        "type": "Microsoft.KeyVault/vaults"
    }

    The "vaultUri" property is the vaultUrl used by azsecrets.NewClient

Authenticate the client

This document demonstrates using DefaultAzureCredential to authenticate as a service principal. However, Client accepts any azidentity credential. See the azidentity documentation for more information about other credentials.

Create a service principal (optional)

This Azure Cloud Shell snippet shows how to create a new service principal. Before using it, replace "your-application-name" with a more appropriate name for your service principal.

Create a service principal:

az ad sp create-for-rbac --name http://my-application --skip-assignment

Output:

{
    "appId": "generated app id",
    "displayName": "my-application",
    "name": "http://my-application",
    "password": "random password",
    "tenant": "tenant id"
}

Use the output to set AZURE_CLIENT_ID ("appId" above), AZURE_CLIENT_SECRET ("password" above) and AZURE_TENANT_ID ("tenant" above) environment variables. The following example shows a way to do this in Bash:

export AZURE_CLIENT_ID="generated app id"
export AZURE_CLIENT_SECRET="random password"
export AZURE_TENANT_ID="tenant id"

Authorize the service principal to perform key operations in your Key Vault:

az keyvault set-policy --name my-key-vault --spn $AZURE_CLIENT_ID --secret-permissions get set list delete backup recover restore purge

Possible permissions:

  • Secret management: set, backup, delete, get, list, purge, recover, restore

If you have enabled role-based access control (RBAC) for Key Vault instead, you can find roles like "Key Vault Secrets Officer" in our RBAC guide.

Create a client

Once the AZURE_CLIENT_ID, AZURE_CLIENT_SECRET and AZURE_TENANT_ID environment variables are set, DefaultAzureCredential will be able to authenticate the Client.

Constructing the client also requires your vault's URL, which you can get from the Azure CLI or the Azure Portal. In the Azure Portal, this URL is the vault's "DNS Name".

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

func main() {
	vaultURL := os.Getenv("AZURE_KEYVAULT_URL")
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		panic(err)
	}

	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		panic(err)
	}
}

Key concepts

Secret

A secret consists of a secret value and its associated metadata and management information. This library handles secret values as strings, but Azure Key Vault doesn't store them as such. For more information about secrets and how Key Vault stores and manages them, see the Key Vault documentation.

Client can set secret values in the vault, update secret metadata, and delete secrets, as shown in the examples below.

Examples

This section contains code snippets covering common tasks:

Set a Secret

SetSecret creates new secrets and changes the values of existing secrets. If no secret with the given name exists, SetSecret creates a new secret with that name and the given value. If the given name is in use, SetSecret creates a new version of that secret, with the given value.

import (
	"context"
	"fmt"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

func main() {
	vaultURL := os.Getenv("AZURE_KEYVAULT_URL")
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		panic(err)
	}

	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		panic(err)
	}

	secretName := "mySecretName"
	secretValue := "mySecretValue"

	resp, err := client.SetSecret(context.TODO(), secretName, secretValue, nil)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Set secret %s", *resp.ID)
}

Retrieve a Secret

GetSecret retrieves a secret previously stored in the Key Vault.

import (
	"context"
	"fmt"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

func main() {
	vaultURL := os.Getenv("AZURE_KEYVAULT_URL")
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		panic(err)
	}

	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		panic(err)
	}

	resp, err := client.GetSecret(context.TODO(), "mySecretName", nil)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Secret Name: %s\tSecret Value: %s", *resp.ID, *resp.Value)
}

Update Secret metadata

UpdateSecretProperties updates a secret's metadata. It cannot change the secret's value; use SetSecret to set a secret's value.

import (
	"context"
	"fmt"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

func main() {
	vaultURL := os.Getenv("AZURE_KEYVAULT_URL")
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		panic(err)
	}

	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		panic(err)
	}

	options := &azsecrets.UpdateSecretPropertiesOptions{
		ContentType: to.StringPtr("password"),
		Tags: map[string]string{
			"Tag1": "TagVal1",
		},
		Properties: &azsecrets.Properties{
			Enabled:   to.BoolPtr(true),
			ExpiresOn: to.TimePtr(time.Now().Add(48 * time.Hour)),
			NotBefore: to.TimePtr(time.Now().Add(-24 * time.Hour)),
		},
	}
	resp, err := client.UpdateSecretProperties(context.Background(), "secret-to-update", options)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Updated secret with ID: %s\n", *resp.ID)
}

Delete a Secret

BeginDeleteSecret requests Key Vault delete a secret, returning a poller which allows you to wait for the deletion to finish. Waiting is helpful when the vault has soft-delete enabled, and you want to purge (permanently delete) the secret as soon as possible. When soft-delete is disabled, BeginDeleteSecret itself is permanent.

import (
	"context"
	"fmt"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

func main() {
	vaultURL := os.Getenv("AZURE_KEYVAULT_URL")
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		panic(err)
	}

	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		panic(err)
	}

	resp, err := client.BeginDeleteSecret(context.TODO(), "secretToDelete", nil)
	if err != nil {
		panic(err)
	}
	// This is optional if you don't care when the secret is deleted
	_, err = resp.PollUntilDone(context.TODO(), 250*time.Millisecond)
	if err != nil {
		panic(err)
	}
}

List secrets

ListSecrets lists the properties of all of the secrets in the client's vault. This list doesn't include the secret's values.

import (
	"context"
	"fmt"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

func main() {
	vaultURL := os.Getenv("AZURE_KEYVAULT_URL")
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		panic(err)
	}

	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		panic(err)
	}

	pager := client.ListSecrets(nil)
	for pager.More() {
		page, err := pager.NextPage(context.TODO())
		if err != nil {
			panic(err)
		}
		for _, v := range page.Secrets {
			fmt.Printf("Secret Name: %s\tSecret Tags: %v\n", *v.ID, v.Tags)
		}
	}
}

Troubleshooting

Error Handling

All I/O operations will return an error that can be investigated to discover more information about the error. In addition, you can investigate the raw response of any response object:

resp, err := client.GetSecret(context.Background(), "mySecretName", nil)
if err != nil {
    var httpErr azcore.ResponseError
    if errors.As(err, &httpErr) {
        // investigate httpErr.RawResponse
    }
}

Logging

This module uses the classification based logging implementation in azcore. To turn on logging set AZURE_SDK_GO_LOGGING to all. If you only want to include logs for azsecrets, you must create your own logger and set the log classification as LogCredential.

To obtain more detailed logging, including request/response bodies and header values, make sure to leave the logger as default or enable the LogRequest and/or LogResponse classificatons. A logger that only includes credential logs can be like the following:

import azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log"
// Set log to output to the console
azlog.SetListener(func(cls azlog.Event, msg string) {
    fmt.Println(msg)
})

// Includes only requests and responses in credential logs
azlog.SetEvents(azlog.EventRequest, azlog.EventResponse)

CAUTION: logs from credentials contain sensitive information. These logs must be protected to avoid compromising account security.

Accessing http.Response

You can access the raw *http.Response returned by the service using the runtime.WithCaptureResponse method and a context passed to any client method.

import "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"

func main() {
    var respFromCtx *http.Response
    ctx := runtime.WithCaptureResponse(context.Background(), &respFromCtx)
    _, err = client.GetSecret(ctx, "mySecretName", nil)
    if err != nil {
        panic(err)
    }
    // Do something with *http.Response
    fmt.Println(respFromCtx.StatusCode)
}

Additional Documentation

For more extensive documentation on Azure Key Vault, see the API reference documentation.

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.

Impressions