Skip to content

Commit 5a5d599

Browse files
committed
wip, idp basic abstraction
1 parent 3f284ca commit 5a5d599

File tree

5 files changed

+384
-24
lines changed

5 files changed

+384
-24
lines changed

confidential_identity_provider.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package entraid
2+
3+
import (
4+
"context"
5+
"crypto"
6+
"errors"
7+
"fmt"
8+
confidential "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
9+
)
10+
11+
import "crypto/x509"
12+
13+
const (
14+
// AuthorityTypeDefault is the default authority type.
15+
// This is used to specify the authority type when requesting a token.
16+
AuthorityTypeDefault = "default"
17+
// AuthorityTypeMultiTenant is the multi-tenant authority type.
18+
// This is used to specify the multi-tenant authority type when requesting a token.
19+
// This type of authority is used to authenticate the identity when requesting a token.
20+
AuthorityTypeMultiTenant = "multi-tenant"
21+
// AuthorityTypeCustom is the custom authority type.
22+
// This is used to specify the custom authority type when requesting a token.
23+
AuthorityTypeCustom = "custom"
24+
)
25+
26+
type AuthorityConfiguration struct {
27+
// AuthorityType is the type of authority used to authenticate with the identity provider.
28+
// This can be either "default", "multi-tenant", or "custom".
29+
AuthorityType string
30+
31+
// Authority is the authority used to authenticate with the identity provider.
32+
// This is typically the URL of the identity provider.
33+
// For example, "https://login.microsoftonline.com/{tenantID}/v2.0"
34+
Authority string
35+
36+
// TenantID is the tenant ID of the identity provider.
37+
// This is used to identify the tenant when requesting a token.
38+
// This is typically the ID of the Azure Active Directory tenant.
39+
TenantID string
40+
}
41+
42+
func (a AuthorityConfiguration) GetAuthority() (string, error) {
43+
if a.AuthorityType == "" {
44+
a.AuthorityType = AuthorityTypeDefault
45+
}
46+
47+
switch a.AuthorityType {
48+
case AuthorityTypeDefault:
49+
return "https://login.microsoftonline.com/common", nil
50+
case AuthorityTypeMultiTenant:
51+
if a.TenantID == "" {
52+
return "", errors.New("tenant ID is required when using multi-tenant authority type")
53+
}
54+
return fmt.Sprintf("https://login.microsoftonline.com/%s", a.TenantID), nil
55+
case AuthorityTypeCustom:
56+
if a.Authority == "" {
57+
return "", errors.New("authority is required when using custom authority type")
58+
}
59+
return a.Authority, nil
60+
default:
61+
return "", errors.New("invalid authority type")
62+
}
63+
}
64+
65+
type ConfidentialIdentityProvider struct {
66+
// clientID is the client ID used to authenticate with the identity provider.
67+
clientID string
68+
69+
// credential is the credential used to authenticate with the identity provider.
70+
credential confidential.Credential
71+
72+
// scopes is the list of scopes used to request a token from the identity provider.
73+
scopes []string
74+
75+
// client confidential is the client used to request a token from the identity provider.
76+
client *confidential.Client
77+
}
78+
79+
type ConfidentialIdentityProviderOptions struct {
80+
// ClientID is the client ID used to authenticate with the identity provider.
81+
ClientID string
82+
83+
// CredentialsType is the type of credentials used to authenticate with the identity provider.
84+
// This can be either "ClientSecret" or "ClientCertificate".
85+
CredentialsType string
86+
87+
// ClientSecret is the client secret used to authenticate with the identity provider.
88+
ClientSecret string
89+
90+
// ClientCert is the client certificate used to authenticate with the identity provider.
91+
ClientCert []*x509.Certificate
92+
// ClientPrivateKey is the private key used to authenticate with the identity provider.
93+
ClientPrivateKey crypto.PrivateKey
94+
95+
// Scopes is the list of scopes used to request a token from the identity provider.
96+
Scopes []string
97+
98+
// Authority is the authority used to authenticate with the identity provider.
99+
Authority AuthorityConfiguration
100+
}
101+
102+
func NewConfidentialIdentityProvider(opts ConfidentialIdentityProviderOptions) (*ConfidentialIdentityProvider, error) {
103+
var credential confidential.Credential
104+
var authority string
105+
var err error
106+
107+
if opts.ClientID == "" {
108+
return nil, errors.New("client ID is required")
109+
}
110+
111+
if opts.CredentialsType != ClientSecretCredentialType && opts.CredentialsType != ClientCertificateCredentialType {
112+
return nil, errors.New("invalid credentials type")
113+
}
114+
115+
// Get the authority from the authority configuration.
116+
authority, err = opts.Authority.GetAuthority()
117+
if err != nil {
118+
return nil, fmt.Errorf("failed to get authority: %w", err)
119+
}
120+
121+
switch opts.CredentialsType {
122+
case ClientSecretCredentialType:
123+
// ClientSecretCredentialType is the type of credentials that uses a client secret to authenticate.
124+
if opts.ClientSecret == "" {
125+
return nil, errors.New("client secret is required when using client secret credentials")
126+
}
127+
128+
credential, err = confidential.NewCredFromSecret(opts.ClientSecret)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to create client secret credential: %w", err)
131+
}
132+
case ClientCertificateCredentialType:
133+
// ClientCertificateCredentialType is the type of credentials that uses a client certificate to authenticate.
134+
if opts.ClientCert == nil {
135+
return nil, errors.New("client certificate is required when using client certificate credentials")
136+
}
137+
if opts.ClientPrivateKey == nil {
138+
return nil, errors.New("client private key is required when using client certificate credentials")
139+
}
140+
credential, err = confidential.NewCredFromCert(opts.ClientCert, opts.ClientPrivateKey)
141+
if err != nil {
142+
return nil, fmt.Errorf("failed to create client certificate credential: %w", err)
143+
}
144+
}
145+
146+
client, err := confidential.New(authority, opts.ClientID, credential)
147+
if err != nil {
148+
return nil, fmt.Errorf("failed to create client: %w", err)
149+
}
150+
151+
if opts.Scopes == nil {
152+
opts.Scopes = []string{RedisScopeDefault}
153+
}
154+
155+
return &ConfidentialIdentityProvider{
156+
clientID: opts.ClientID,
157+
credential: credential,
158+
scopes: opts.Scopes,
159+
client: &client,
160+
}, nil
161+
}
162+
163+
func (c *ConfidentialIdentityProvider) RequestToken() (string, error) {
164+
if c.client == nil {
165+
return "", errors.New("client is not initialized")
166+
}
167+
168+
result, err := c.client.AcquireTokenByCredential(context.TODO(), c.scopes)
169+
if err != nil {
170+
return "", fmt.Errorf("failed to acquire token: %w", err)
171+
}
172+
173+
return result.AccessToken, nil
174+
}

credentials_provider.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,6 @@ func (e *entraidCredentialsProvider) Subscribe(listener auth.CredentialsListener
9393
return credentials, cancel, nil
9494
}
9595

96-
// newCredentialsProvider creates a new credentials provider.
97-
// It takes a TokenManager and CredentialProviderOptions as arguments and returns a StreamingCredentialsProvider interface.
98-
// The TokenManager is used to obtain the token, and the CredentialProviderOptions contains options for the credentials provider.
99-
// The credentials provider is responsible for managing the credentials and refreshing them when necessary.
100-
// It returns an error if the token manager cannot be started.
101-
func newCredentialsProvider(tokenManager TokenManager, options CredentialProviderOptions) (auth.StreamingCredentialsProvider, error) {
102-
cp := &entraidCredentialsProvider{
103-
tokenManager: tokenManager,
104-
options: options,
105-
}
106-
cancelTokenManager, err := cp.tokenManager.Start(tokenListenerFromCP(cp))
107-
if err != nil {
108-
return nil, fmt.Errorf("couldn't start token manager: %w", err)
109-
}
110-
cp.cancelTokenManager = cancelTokenManager
111-
return cp, nil
112-
}
113-
11496
type entraidTokenListener struct {
11597
onTokenNext func(token *Token)
11698
onTokenError func(err error)
@@ -130,3 +112,21 @@ func (l *entraidTokenListener) OnTokenNext(token *Token) {
130112
func (l *entraidTokenListener) OnTokenError(err error) {
131113
l.onTokenError(err)
132114
}
115+
116+
// newCredentialsProvider creates a new credentials provider.
117+
// It takes a TokenManager and CredentialProviderOptions as arguments and returns a StreamingCredentialsProvider interface.
118+
// The TokenManager is used to obtain the token, and the CredentialProviderOptions contains options for the credentials provider.
119+
// The credentials provider is responsible for managing the credentials and refreshing them when necessary.
120+
// It returns an error if the token manager cannot be started.
121+
func newCredentialsProvider(tokenManager TokenManager, options CredentialsProviderOptions) (auth.StreamingCredentialsProvider, error) {
122+
cp := &entraidCredentialsProvider{
123+
tokenManager: tokenManager,
124+
options: options,
125+
}
126+
cancelTokenManager, err := cp.tokenManager.Start(tokenListenerFromCP(cp))
127+
if err != nil {
128+
return nil, fmt.Errorf("couldn't start token manager: %w", err)
129+
}
130+
cp.cancelTokenManager = cancelTokenManager
131+
return cp, nil
132+
}

managed_identity_provider.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package entraid
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
mi "github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity"
9+
)
10+
11+
type ManagedIdentityProvider struct {
12+
// UserAssignedClientID is the client ID of the user assigned identity.
13+
// This is used to identify the identity when requesting a token.
14+
UserAssignedClientID string
15+
16+
// ManagedIdentityType is the type of managed identity.
17+
// This can be either SystemAssigned or UserAssigned.
18+
ManagedIdentityType string
19+
20+
// Scopes is a list of scopes that the identity has access to.
21+
// This is used to specify the permissions that the identity has when requesting a token.
22+
Scopes []string
23+
24+
// Client is the managed identity client used to request a token.
25+
Client *mi.Client
26+
}
27+
28+
type ManagedIdentityProviderOptions struct {
29+
// UserAssignedClientID is the client ID of the user assigned identity.
30+
// This is used to identify the identity when requesting a token.
31+
UserAssignedClientID string
32+
// ManagedIdentityType is the type of managed identity.
33+
// This can be either SystemAssigned or UserAssigned.
34+
ManagedIdentityType string
35+
// Scopes is a list of scopes that the identity has access to.
36+
// This is used to specify the permissions that the identity has when requesting a token.
37+
Scopes []string
38+
}
39+
40+
func NewManagedIdentityProvider(opts ManagedIdentityProviderOptions) (*ManagedIdentityProvider, error) {
41+
var client mi.Client
42+
var err error
43+
44+
if opts.ManagedIdentityType != SystemAssignedIdentity && opts.ManagedIdentityType != UserAssignedIdentity {
45+
return nil, errors.New("invalid managed identity type")
46+
}
47+
48+
switch opts.ManagedIdentityType {
49+
case SystemAssignedIdentity:
50+
// SystemAssignedIdentity is the type of identity that is automatically managed by Azure.
51+
// This type of identity is automatically created and managed by Azure.
52+
// It is used to authenticate the identity when requesting a token.
53+
client, err = mi.New(mi.SystemAssigned())
54+
case UserAssignedIdentity:
55+
// UserAssignedIdentity is required to be specified when using a user assigned identity.
56+
if opts.UserAssignedClientID == "" {
57+
return nil, errors.New("user assigned client ID is required when using user assigned identity")
58+
}
59+
// UserAssignedIdentity is the type of identity that is managed by the user.
60+
client, err = mi.New(mi.UserAssignedClientID(opts.UserAssignedClientID))
61+
}
62+
63+
if err != nil {
64+
return nil, fmt.Errorf("couldn't create managed identity client: %w", err)
65+
}
66+
67+
return &ManagedIdentityProvider{
68+
UserAssignedClientID: opts.UserAssignedClientID,
69+
ManagedIdentityType: opts.ManagedIdentityType,
70+
Scopes: opts.Scopes,
71+
Client: &client,
72+
}, nil
73+
}
74+
75+
func (m *ManagedIdentityProvider) requestToken() (string, error) {
76+
if m.Client == nil {
77+
return "", errors.New("managed identity client is not initialized")
78+
}
79+
80+
// default resource is RedisResource == "https://redis.azure.com"
81+
// if no scopes are provided, use the default resource
82+
// if scopes are provided, use the first scope as the resource
83+
resource := RedisResource
84+
if len(m.Scopes) > 0 {
85+
resource = m.Scopes[0]
86+
}
87+
// acquire token using the managed identity client
88+
// the resource is the URL of the resource that the identity has access to
89+
token, err := m.Client.AcquireToken(context.TODO(), resource)
90+
if err != nil {
91+
return "", fmt.Errorf("coudn't acquire token: %w", err)
92+
}
93+
94+
// return the access token
95+
return token.AccessToken, nil
96+
}

0 commit comments

Comments
 (0)