Skip to content

Commit a023301

Browse files
azkv: enable specifying some auth methods directly, and add cachable user authentication methods
1 parent 146d12c commit a023301

File tree

2 files changed

+140
-3
lines changed

2 files changed

+140
-3
lines changed

azkv/keysource.go

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ import (
99
"context"
1010
"encoding/base64"
1111
"fmt"
12+
"path/filepath"
1213
"regexp"
1314
"strings"
1415
"time"
1516

17+
"encoding/json"
18+
"os"
19+
1620
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
21+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
1722
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
1823
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
24+
azidentitycache "github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
1925
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
2026
"github.com/sirupsen/logrus"
2127

@@ -25,6 +31,8 @@ import (
2531
const (
2632
// KeyTypeIdentifier is the string used to identify an Azure Key Vault MasterKey.
2733
KeyTypeIdentifier = "azure_kv"
34+
35+
sopsAzureAuthMethod = "SOPS_AZURE_AUTH_METHOD"
2836
)
2937

3038
var (
@@ -230,7 +238,132 @@ func (key *MasterKey) TypeToIdentifier() string {
230238
// azidentity.NewDefaultAzureCredential.
231239
func (key *MasterKey) getTokenCredential() (azcore.TokenCredential, error) {
232240
if key.tokenCredential == nil {
233-
return azidentity.NewDefaultAzureCredential(nil)
241+
242+
authMethod := strings.ToUpper(os.Getenv(sopsAzureAuthMethod))
243+
switch authMethod {
244+
case "BROWSER":
245+
246+
return cachedInteractiveBrowserCredentials()
247+
248+
case "DEVICE_CODE":
249+
return cachedDeviceCodeCredentials()
250+
case "AZURE_CLI":
251+
return azidentity.NewAzureCLICredential(nil)
252+
case "MSI":
253+
return azidentity.NewManagedIdentityCredential(nil)
254+
// If "DEFAULT" or not explicitly specified then use the default authentication chain.
255+
case "", "DEFAULT":
256+
return azidentity.NewDefaultAzureCredential(nil)
257+
default:
258+
return nil, fmt.Errorf("Value `%s` is unsupported for `%s`, to resolve this either leave it unset or use one of DEFAULT/MSI/BROWSER/DEVICE_CODE", authMethod, sopsAzureAuthMethod)
259+
}
234260
}
235261
return key.tokenCredential, nil
236262
}
263+
264+
func sopsCacheDir() (string, error) {
265+
userCacheDir, err := os.UserCacheDir()
266+
if err != nil {
267+
return "", err
268+
}
269+
270+
cacheDir := filepath.Join(userCacheDir, "/sops")
271+
272+
if err = os.MkdirAll(cacheDir, 0o700); err != nil {
273+
return "", err
274+
}
275+
276+
return cacheDir, nil
277+
}
278+
279+
type CachableTokenCredential interface {
280+
Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error)
281+
GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error)
282+
}
283+
284+
func cacheStoreRecord(cachePath string, record azidentity.AuthenticationRecord) error {
285+
b, err := json.Marshal(record)
286+
if err != nil {
287+
return err
288+
}
289+
290+
return os.WriteFile(cachePath, b, 0600)
291+
}
292+
293+
func cacheLoadRecord(cachePath string) (azidentity.AuthenticationRecord, error) {
294+
var record azidentity.AuthenticationRecord
295+
296+
b, err := os.ReadFile(cachePath)
297+
if err != nil {
298+
return record, err
299+
}
300+
301+
err = json.Unmarshal(b, &record)
302+
if err != nil {
303+
return record, err
304+
}
305+
306+
return record, nil
307+
}
308+
309+
func cacheTokenCredential(cachePath string, tokenCredentialFn func(cache azidentity.Cache, record azidentity.AuthenticationRecord) (CachableTokenCredential, error)) (azcore.TokenCredential, error) {
310+
cache, err := azidentitycache.New(nil)
311+
// Errors if persistent caching is not supported by the current runtime
312+
if err != nil {
313+
return nil, err
314+
}
315+
316+
cachedRecord, cacheLoadErr := cacheLoadRecord(cachePath)
317+
318+
credential, err := tokenCredentialFn(cache, cachedRecord)
319+
if err != nil {
320+
return nil, err
321+
}
322+
323+
// If loading the authenticationRecord from the cachePath failed for any reason (validation, file doesn't exist, not encoded using json, etc.)
324+
if cacheLoadErr != nil {
325+
record, err := credential.Authenticate(context.Background(), nil)
326+
if err != nil {
327+
return nil, err
328+
}
329+
330+
if err = cacheStoreRecord(cachePath, record); err != nil {
331+
return nil, err
332+
}
333+
}
334+
335+
return credential, nil
336+
}
337+
338+
func cachedInteractiveBrowserCredentials() (azcore.TokenCredential, error) {
339+
cacheDir, err := sopsCacheDir()
340+
if err != nil {
341+
return nil, err
342+
}
343+
return cacheTokenCredential(
344+
filepath.Join(cacheDir, "azure_auth_record_browser.json"),
345+
func(cache azidentity.Cache, record azidentity.AuthenticationRecord) (CachableTokenCredential, error) {
346+
return azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{
347+
AuthenticationRecord: record,
348+
Cache: cache,
349+
})
350+
},
351+
)
352+
}
353+
354+
func cachedDeviceCodeCredentials() (azcore.TokenCredential, error) {
355+
cacheDir, err := sopsCacheDir()
356+
if err != nil {
357+
return nil, err
358+
}
359+
360+
return cacheTokenCredential(
361+
filepath.Join(cacheDir, "azure_auth_record_device_code.json"),
362+
func(cache azidentity.Cache, record azidentity.AuthenticationRecord) (CachableTokenCredential, error) {
363+
return azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
364+
AuthenticationRecord: record,
365+
Cache: cache,
366+
})
367+
},
368+
)
369+
}

go.mod

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
module github.com/getsops/sops/v3
22

3-
go 1.22
4-
toolchain go1.23.6
3+
go 1.23.0
4+
5+
toolchain go1.24.0
56

67
require (
78
cloud.google.com/go/kms v1.21.0
89
cloud.google.com/go/storage v1.50.0
910
filippo.io/age v1.2.1
1011
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
1112
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
13+
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2
1214
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1
1315
github.com/ProtonMail/go-crypto v1.1.5
1416
github.com/aws/aws-sdk-go-v2 v1.36.2
@@ -60,6 +62,7 @@ require (
6062
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
6163
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
6264
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
65+
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 // indirect
6366
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
6467
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
6568
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
@@ -112,6 +115,7 @@ require (
112115
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
113116
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
114117
github.com/hashicorp/hcl v1.0.0 // indirect
118+
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 // indirect
115119
github.com/kylelemons/godebug v1.1.0 // indirect
116120
github.com/mattn/go-colorable v0.1.13 // indirect
117121
github.com/mattn/go-isatty v0.0.20 // indirect

0 commit comments

Comments
 (0)