summaryrefslogtreecommitdiff
path: root/content/posts/interacting_with_azure_keyvault_in_go.md
blob: fa3d6a546cae95d58232b5b415063b3bc5f678d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
---
title: 'Interacting with Azure Key Vault in Go'
description: "Most times when working with API's there some kind of documentation on how to iteract with it. Working with Azure SDK for Go is a different story. There's almost no documentation (except the code itself)."
tags: ["go", "golang", "azure", "keyvault"]
date: 2021-08-20T23:18:32+02:00
draft: false
---

Most times when working with API's there some kind of documentation on how to iteract with it. Working with Azure SDK for Go is a different story. There's almost no documentation (except the code itself). At my current job we use Azure a lot and a big part of that is Azure Key Vault. For my latest project I had to fetch some secrets from Key Vault to use in a CLI application, so I had to start digging into the source code to find how to interact with it.

<!--more-->

## 1. Authentication

Almost any endpoint in the Azure API requires authentication, so let's start with that. Services in the Azure API, for the most part, use the `autorest/azure/auth` module for handling authentication, but for Key Vault it is a bit different. For Key Vaults we have two modules; one for managing Key Vaults, and one for working with the data.

- Management: github.com/Azure/go-autorest/autorest/azure/auth
- Data: github.com/Azure/azure-sdk-for-go/services/keyvault/auth

This is very important, because if you type "auth.NewAuthorizerFromCLI" in your editor and have auto imports on, it will most likely use the autorest module, which will give you an error when working with the data inside Key Vault.

Another important concept to know, is that the Azure SDK for Go use something called "Authorizer". As we'll see in this sample, we need to initiate an "authorizer" from one of the auth modules, and then pass that into the module for the specific service, Key Vault in this scenario.

### Methods

When authorizing with the Azure SDK, there are three methods to choose from:

- NewAuthorizerFromCLI
- NewAuthorizerFromEnvironment
- NewAuthorizerFromFile

#### NewAuthorizerFromCLI

If you have `az cli` installed, you can authenticate using your current az user. To show your current logged in account you can run `az account show` or `az login` to login. This may be the easiest option.

#### NewAuthorizerFromEnvironment

This will allow you to authorize using environment variables. It will look for variables belonging to different authentication mechanism in this order:

1. Client credentials
2. Client certificate
3. Username password
4. MSI

It will determine the method to use based on which of these environment variables are set:

- AZURE_SUBSCRIPTION_ID
- AZURE_TENANT_ID
- AZURE_CLIENT_ID
- AZURE_CLIENT_SECRET
- AZURE_CERTIFICATE_PATH
- AZURE_CERTIFICATE_PASSWORD
- AZURE_USERNAME
- AZURE_PASSWORD

#### NewAuthorizerFromFile

This method allows you to place credentials in a JSON file, and export an environment variable `AZURE_AUTH_LOCATION` that tells the Azure SDK where to look for the file. This file can either be created manually, or you can use the output from `az cli` when creating a new service principal. For example:

```bash
moiaune@box:~$ az ad sp create-for-rbac --sdk-auth > azureauth.json
moiaune@box:~$ cat azureauth.json
{
  "clientId": "b52dd125-9272-4b21-9862-0be667bdf6dc",
  "clientSecret": "ebc6e170-72b2-4b6f-9de2-99410964d2d0",
  "subscriptionId": "ffa52f27-be12-4cad-b1ea-c2c241b6cceb",
  "tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
  "resourceManagerEndpointUrl": "https://management.azure.com/",
  "activeDirectoryGraphResourceId": "https://graph.windows.net/",
  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
  "galleryEndpointUrl": "https://gallery.azure.com/",
  "managementEndpointUrl": "https://management.core.windows.net/"
}
moiaune@box:~$ export AZURE_AUTH_LOCATION=/home/moiaune/azureauth.json
```

> NOTE: REMEMBER TO STORE THE FILE IN A SECURE LOCATION

### Example

Now, let's see this in action. I'm going to use the `NewAuthorizerFromCLI` method because its the simplest. First I need to make sure that I'm logged in to the correct account and subscription. So I run `az login` and a website will poup in my browser, telling me to log in. When thats done, you can run `az account show` to make sure that you are logged in with the correct user and subscription.

```go
package main

import (
    "github.com/Azure/azure-sdk-for-go/services/keyvault/auth"
    "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
)

var (
    // for simplicity we make our client global
    client keyvault.BaseClient
)

func main() {
    // we initiate our Key Vault client
    client = keyvault.New()

    // then we initiate our authorizer
    authorizer, err := auth.NewAuthorizerFromCLI()

    // and tell our client to authenticate using that
    client.Authorizer = authorizer
}
```

This is literally it. As long as you are successfully logged into `az cli`, you are now ready to work with data in Key Vault using Go.

## 2. Fetching a Key Vault secret

Now that we have learned how to authenticate, let's try to do something usefull, like fetching a secret from Azure Key Vault.

In the Azure SDK, if you want to get a specific secret, you must provide it with a version. In most scenarios we want the latest version, so lets first write a function that will list all versions and give us the latest one. We will then write another function that fetch a secret based on latest version. We will continue to build on the code above.

```go
// ...

const (
    // name of our Key Vault
    vaultName = "example-vault-01"

    // this will build the BaseURI for our Key Vault
    vaultBaseURI = fmt.Sprintf("https://%s.%s", vaultName, azure.PublicCloud.KeyVaultDNSSuffix)
)

func getLatestVersion(secretName string) (string, error) {
    // let's fetch all versions
    list, err := client.GetSecretVersionsComplete(context.Background(), vaultBaseURI, secretName, nil)
    if err != nil {
        return "", err
    }

    var lastDate time.Time
    var lastVersion string

    // loop through all versions
    for list.NotDone() {

        v := list.Value()

        // make sure to only check for secrets that are enabled
        if *v.Attributes.Enabled {
            updated := time.Time(*v.Attributes.Updated)

            // if lastDate is not set, or current version is newer than lastDate;
            // update lastDate
            if lastDate.IsZero() || updated.After(lastDate) {
                lastDate = updated
            }

            // split the ID on '/' and get the last part which is the version hash
            parts := strings.Split(*v.ID, "/")
            lastVersion = parts[len(parts)-1]
        }

        list.Next()
    }

    return lastVersion, nil
}
```

Essentially what this code does is get all versions for a spesific secret, then loop through them to find the newest one that is also enabled. Split the ID field on the '/' and get the last part which is the version hash.

Now that we have a method to get the newest version hash, we can build our function for fetching the secret itself. We continue to build on our code from above.

```go
// ...

func getSecret(secretName string) (string, error) (
    // get latest version for our secret
    latestVersion, err := getLatestVersion(secretName)
    if err != nil {
        return "", err
    }

    // get secret itself
    secret, err := client.GetSecret(context.Background(), vaultBaseURI, secretName, latestVersion)
    if err != nil {
        return "", err
    }

    // only return the value a.k.a THE secret
    return *secret.Value, nil
}
```

First we get our latest version using our `getSecretVersion()` function, then we get the secret. The `GetSecret()` function returns a `SecretBundle` which contains some meta-data and other stuff, but in this example we're only interrested in the `Value` which is the actual secret.

If we put it all together it will look like this.

```go
package main

import (
    "context"
    "fmt"
    "strings"
    "time"

    "github.com/Azure/azure-sdk-for-go/services/keyvault/auth"
    "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
    "github.com/Azure/go-autorest/autorest/azure"
)

const (
    vaultName = "example-vault-01"
    vaultBaseURI = fmt.Sprintf("https://%s.%s", vaultName, azure.PublicCloud.KeyVaultDNSSuffix)
)

var (
    client keyvault.BaseClient
)

func main() {
    client = keyvault.New()

    authorizer, err := auth.NewAuthorizerFromCLI()

    client.Authorizer = authorizer

    secretValue, err := getSecret("example-secret")
    if err != nil {
        fmt.Println("An error occured:", err)
        return
    }

    fmt.Println("Secret Value:", secretValue)
}

func getLatestVersion(secretName string) (string, error) {
    list, err := client.GetSecretVersionsComplete(context.Background(), vaultBaseURI, secretName, nil)
    if err != nil {
        return "", err
    }

    var lastDate time.Time
    var lastVersion string

    for list.NotDone() {

        v := list.Value()

        if *v.Attributes.Enabled {
            updated := time.Time(*v.Attributes.Updated)

            if lastDate.IsZero() || updated.After(lastDate) {
                lastDate = updated
            }

            parts := strings.Split(*v.ID, "/")
            lastVersion = parts[len(parts)-1]
        }

        list.Next()
    }

    return lastVersion, nil
}

func getSecret(secretName string) (string, error) (
    latestVersion, err := getLatestVersion(secretName)
    if err != nil {
        return "", err
    }

    secret, err := client.GetSecret(context.Background(), vaultBaseURI, secretName, latestVersion)
    if err != nil {
        return "", err
    }

    return *secret.Value, nil
}
```

This should output:

```bash
// Output
Secret Value: hunter2
```

## Wrapping up

So now you know how to fetch secrets from Azure Key Vault using Go. If you want to interact with other services in the Azure SDK, the process is pretty much the same.

1. Create a client from the service
2. Initiate an authorizer
3. Set the client to use the authorizer

The SDK is pretty well written and easy to understand when you just grasp the process. So it's not that difficult to dive into the source code to find answers. Tho, I still prefer proper documentation.