master
go 153 lines 4.81 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package cloudauth
4
5 import (
6 "errors"
7 "fmt"
8 "strings"
9
10 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
11 "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
12 )
13
14 const (
15 AzureADAuthModeServicePrincipal = "service_principal"
16 AzureADAuthModeManagedIdentity = "managed_identity"
17 AzureADAuthModeDefault = "default"
18
19 azureADAuthConfigPath = "cloud_auth.azure_ad"
20 )
21
22 type AzureADModeServicePrincipalConfig struct {
23 TenantID string `yaml:"tenant_id,omitempty" json:"tenant_id,omitempty"`
24 ClientID string `yaml:"client_id,omitempty" json:"client_id,omitempty"`
25 ClientSecret string `yaml:"client_secret,omitempty" json:"client_secret,omitempty"`
26 }
27
28 type AzureADModeManagedIdentityConfig struct {
29 ClientID string `yaml:"client_id,omitempty" json:"client_id,omitempty"`
30 }
31
32 type AzureADAuthConfig struct {
33 Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
34 ModeServicePrincipal *AzureADModeServicePrincipalConfig `yaml:"mode_service_principal,omitempty" json:"mode_service_principal,omitempty"`
35 ModeManagedIdentity *AzureADModeManagedIdentityConfig `yaml:"mode_managed_identity,omitempty" json:"mode_managed_identity,omitempty"`
36 }
37
38 type AzureADCredentialOptions struct {
39 ClientOptions azcore.ClientOptions
40 }
41
42 func (c AzureADAuthConfig) NormalizedMode() string {
43 return strings.ToLower(strings.TrimSpace(c.Mode))
44 }
45
46 func (c AzureADAuthConfig) Validate() error {
47 return c.ValidateWithPath(azureADAuthConfigPath)
48 }
49
50 func (c AzureADAuthConfig) ValidateWithPath(path string) error {
51 modeField := fieldPath(path, "mode")
52 mode := c.NormalizedMode()
53
54 if mode == "" {
55 return errors.New(modeField + " is required")
56 }
57
58 switch mode {
59 case AzureADAuthModeServicePrincipal:
60 var errs []error
61 if c.ModeServicePrincipal == nil {
62 return fmt.Errorf("%s is required when %s is %q", fieldPath(path, "mode_service_principal"), modeField, AzureADAuthModeServicePrincipal)
63 }
64
65 if strings.TrimSpace(c.ModeServicePrincipal.TenantID) == "" {
66 errs = append(errs, errors.New(fieldPath(path, "mode_service_principal.tenant_id")+" is required"))
67 }
68 if strings.TrimSpace(c.ModeServicePrincipal.ClientID) == "" {
69 errs = append(errs, errors.New(fieldPath(path, "mode_service_principal.client_id")+" is required"))
70 }
71 if strings.TrimSpace(c.ModeServicePrincipal.ClientSecret) == "" {
72 errs = append(errs, errors.New(fieldPath(path, "mode_service_principal.client_secret")+" is required"))
73 }
74 return errors.Join(errs...)
75 case AzureADAuthModeManagedIdentity, AzureADAuthModeDefault:
76 return nil
77 default:
78 return fmt.Errorf("%s %q is invalid: expected one of %q, %q, %q",
79 modeField, c.Mode, AzureADAuthModeServicePrincipal, AzureADAuthModeManagedIdentity, AzureADAuthModeDefault)
80 }
81 }
82
83 func (c AzureADAuthConfig) NewCredential() (azcore.TokenCredential, error) {
84 if err := c.Validate(); err != nil {
85 return nil, err
86 }
87
88 return c.newCredential(nil)
89 }
90
91 func (c AzureADAuthConfig) NewCredentialWithOptions(opts *AzureADCredentialOptions) (azcore.TokenCredential, error) {
92 if err := c.Validate(); err != nil {
93 return nil, err
94 }
95
96 return c.newCredential(opts)
97 }
98
99 func (c AzureADAuthConfig) newCredential(opts *AzureADCredentialOptions) (azcore.TokenCredential, error) {
100 switch c.NormalizedMode() {
101 case AzureADAuthModeServicePrincipal:
102 cfg := c.servicePrincipalConfig()
103 credOpts := &azidentity.ClientSecretCredentialOptions{}
104 if opts != nil {
105 credOpts.ClientOptions = opts.ClientOptions
106 }
107 return azidentity.NewClientSecretCredential(
108 strings.TrimSpace(cfg.TenantID),
109 strings.TrimSpace(cfg.ClientID),
110 strings.TrimSpace(cfg.ClientSecret),
111 credOpts,
112 )
113 case AzureADAuthModeManagedIdentity:
114 cfg := c.managedIdentityConfig()
115 credOpts := &azidentity.ManagedIdentityCredentialOptions{}
116 if opts != nil {
117 credOpts.ClientOptions = opts.ClientOptions
118 }
119 if strings.TrimSpace(cfg.ClientID) != "" {
120 credOpts.ID = azidentity.ClientID(strings.TrimSpace(cfg.ClientID))
121 }
122 return azidentity.NewManagedIdentityCredential(credOpts)
123 case AzureADAuthModeDefault:
124 credOpts := &azidentity.DefaultAzureCredentialOptions{}
125 if opts != nil {
126 credOpts.ClientOptions = opts.ClientOptions
127 }
128 return azidentity.NewDefaultAzureCredential(credOpts)
129 default:
130 return nil, fmt.Errorf("%s %q is invalid", fieldPath(azureADAuthConfigPath, "mode"), c.Mode)
131 }
132 }
133
134 func (c AzureADAuthConfig) servicePrincipalConfig() AzureADModeServicePrincipalConfig {
135 if c.ModeServicePrincipal == nil {
136 return AzureADModeServicePrincipalConfig{}
137 }
138 return *c.ModeServicePrincipal
139 }
140
141 func (c AzureADAuthConfig) managedIdentityConfig() AzureADModeManagedIdentityConfig {
142 if c.ModeManagedIdentity == nil {
143 return AzureADModeManagedIdentityConfig{}
144 }
145 return *c.ModeManagedIdentity
146 }
147
148 func fieldPath(path, field string) string {
149 if path == "" {
150 return field
151 }
152 return path + "." + field
153 }