1//go:build go1.18
2// +build go1.18
3
4// Copyright (c) Microsoft Corporation. All rights reserved.
5// Licensed under the MIT License.
6
7package azidentity
8
9import (
10 "context"
11 "fmt"
12 "strings"
13
14 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
15 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
16 "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
17 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
18)
19
20const credNameManagedIdentity = "ManagedIdentityCredential"
21
22type managedIdentityIDKind int
23
24const (
25 miClientID managedIdentityIDKind = 0
26 miResourceID managedIdentityIDKind = 1
27)
28
29// ManagedIDKind identifies the ID of a managed identity as either a client or resource ID
30type ManagedIDKind interface {
31 fmt.Stringer
32 idKind() managedIdentityIDKind
33}
34
35// ClientID is the client ID of a user-assigned managed identity.
36type ClientID string
37
38func (ClientID) idKind() managedIdentityIDKind {
39 return miClientID
40}
41
42// String returns the string value of the ID.
43func (c ClientID) String() string {
44 return string(c)
45}
46
47// ResourceID is the resource ID of a user-assigned managed identity.
48type ResourceID string
49
50func (ResourceID) idKind() managedIdentityIDKind {
51 return miResourceID
52}
53
54// String returns the string value of the ID.
55func (r ResourceID) String() string {
56 return string(r)
57}
58
59// ManagedIdentityCredentialOptions contains optional parameters for ManagedIdentityCredential.
60type ManagedIdentityCredentialOptions struct {
61 azcore.ClientOptions
62
63 // ID is the ID of a managed identity the credential should authenticate. Set this field to use a specific identity
64 // instead of the hosting environment's default. The value may be the identity's client ID or resource ID, but note that
65 // some platforms don't accept resource IDs.
66 ID ManagedIDKind
67
68 // dac indicates whether the credential is part of DefaultAzureCredential. When true, and the environment doesn't have
69 // configuration for a specific managed identity API, the credential tries to determine whether IMDS is available before
70 // sending its first token request. It does this by sending a malformed request with a short timeout. Any response to that
71 // request is taken to mean IMDS is available, in which case the credential will send ordinary token requests thereafter
72 // with no special timeout. The purpose of this behavior is to prevent a very long timeout when IMDS isn't available.
73 dac bool
74}
75
76// ManagedIdentityCredential authenticates an Azure managed identity in any hosting environment supporting managed identities.
77// This credential authenticates a system-assigned identity by default. Use ManagedIdentityCredentialOptions.ID to specify a
78// user-assigned identity. See Microsoft Entra ID documentation for more information about managed identities:
79// https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview
80type ManagedIdentityCredential struct {
81 client *confidentialClient
82 mic *managedIdentityClient
83}
84
85// NewManagedIdentityCredential creates a ManagedIdentityCredential. Pass nil to accept default options.
86func NewManagedIdentityCredential(options *ManagedIdentityCredentialOptions) (*ManagedIdentityCredential, error) {
87 if options == nil {
88 options = &ManagedIdentityCredentialOptions{}
89 }
90 mic, err := newManagedIdentityClient(options)
91 if err != nil {
92 return nil, err
93 }
94 cred := confidential.NewCredFromTokenProvider(mic.provideToken)
95
96 // It's okay to give MSAL an invalid client ID because MSAL will use it only as part of a cache key.
97 // ManagedIdentityClient handles all the details of authentication and won't receive this value from MSAL.
98 clientID := "SYSTEM-ASSIGNED-MANAGED-IDENTITY"
99 if options.ID != nil {
100 clientID = options.ID.String()
101 }
102 // similarly, it's okay to give MSAL an incorrect tenant because MSAL won't use the value
103 c, err := newConfidentialClient("common", clientID, credNameManagedIdentity, cred, confidentialClientOptions{
104 ClientOptions: options.ClientOptions,
105 })
106 if err != nil {
107 return nil, err
108 }
109 return &ManagedIdentityCredential{client: c, mic: mic}, nil
110}
111
112// GetToken requests an access token from the hosting environment. This method is called automatically by Azure SDK clients.
113func (c *ManagedIdentityCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
114 var err error
115 ctx, endSpan := runtime.StartSpan(ctx, credNameManagedIdentity+"."+traceOpGetToken, c.client.azClient.Tracer(), nil)
116 defer func() { endSpan(err) }()
117
118 if len(opts.Scopes) != 1 {
119 err = fmt.Errorf("%s.GetToken() requires exactly one scope", credNameManagedIdentity)
120 return azcore.AccessToken{}, err
121 }
122 // managed identity endpoints require a Microsoft Entra ID v1 resource (i.e. token audience), not a v2 scope, so we remove "/.default" here
123 opts.Scopes = []string{strings.TrimSuffix(opts.Scopes[0], defaultSuffix)}
124 tk, err := c.client.GetToken(ctx, opts)
125 return tk, err
126}
127
128var _ azcore.TokenCredential = (*ManagedIdentityCredential)(nil)