Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions sdk/azidentity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@
```
* Removed `InteractiveBrowserCredentialOptions.ClientSecret` and `.Port`
* Removed `AADAuthenticationFailedError`
* Removed `id` parameter of `NewManagedIdentityCredential()`. User assigned identities are now
specified by `ManagedIdentityCredentialOptions.ID`:
```go
// before
cred, err := NewManagedIdentityCredential("client-id", nil)
// or, for a resource ID
opts := &ManagedIdentityCredentialOptions{ID: ResourceID}
cred, err := NewManagedIdentityCredential("/subscriptions/...", opts)

// after
clientID := ClientID("7cf7db0d-...")
opts := &ManagedIdentityCredentialOptions{ID: clientID}
// or, for a resource ID
resID: ResourceID("/subscriptions/...")
opts := &ManagedIdentityCredentialOptions{ID: resID}
cred, err := NewManagedIdentityCredential(opts)
```

### Features Added
* Added connection configuration options to `DefaultAzureCredentialOptions`
Expand Down
2 changes: 1 addition & 1 deletion sdk/azidentity/default_azure_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func NewDefaultAzureCredential(options *DefaultAzureCredentialOptions) (*Chained
errMsg += err.Error()
}

msiCred, err := NewManagedIdentityCredential("", &ManagedIdentityCredentialOptions{HTTPClient: options.HTTPClient,
msiCred, err := NewManagedIdentityCredential(&ManagedIdentityCredentialOptions{HTTPClient: options.HTTPClient,
Logging: options.Logging,
Telemetry: options.Telemetry,
})
Expand Down
66 changes: 36 additions & 30 deletions sdk/azidentity/managed_identity_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type managedIdentityClient struct {
imdsAvailableTimeout time.Duration
msiType msiType
endpoint string
id ManagedIdentityIDKind
id ManagedIDKind
unavailableMessage string
}

Expand Down Expand Up @@ -92,12 +92,12 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) *manage
// ctx: The current context for controlling the request lifetime.
// clientID: The client (application) ID of the service principal.
// scopes: The scopes required for the token.
func (c *managedIdentityClient) authenticate(ctx context.Context, clientID string, scopes []string) (*azcore.AccessToken, error) {
func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKind, scopes []string) (*azcore.AccessToken, error) {
if len(c.unavailableMessage) > 0 {
return nil, &CredentialUnavailableError{credentialType: "Managed Identity Credential", message: c.unavailableMessage}
}

msg, err := c.createAuthRequest(ctx, clientID, scopes)
msg, err := c.createAuthRequest(ctx, id, scopes)
if err != nil {
return nil, err
}
Expand All @@ -112,7 +112,7 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, clientID strin
}

if c.msiType == msiTypeIMDS && resp.StatusCode == 400 {
if len(clientID) > 0 {
if id != nil {
return nil, &AuthenticationFailedError{msg: "The requested identity isn't assigned to this resource."}
}
c.unavailableMessage = "No default identity is assigned to this resource."
Expand Down Expand Up @@ -163,12 +163,12 @@ func (c *managedIdentityClient) createAccessToken(res *http.Response) (*azcore.A
}
}

func (c *managedIdentityClient) createAuthRequest(ctx context.Context, clientID string, scopes []string) (*policy.Request, error) {
func (c *managedIdentityClient) createAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
switch c.msiType {
case msiTypeIMDS:
return c.createIMDSAuthRequest(ctx, clientID, scopes)
return c.createIMDSAuthRequest(ctx, id, scopes)
case msiTypeAppServiceV20170901, msiTypeAppServiceV20190801:
return c.createAppServiceAuthRequest(ctx, clientID, scopes)
return c.createAppServiceAuthRequest(ctx, id, scopes)
case msiTypeAzureArc:
// need to perform preliminary request to retreive the secret key challenge provided by the HIMDS service
key, err := c.getAzureArcSecretKey(ctx, scopes)
Expand All @@ -177,9 +177,9 @@ func (c *managedIdentityClient) createAuthRequest(ctx context.Context, clientID
}
return c.createAzureArcAuthRequest(ctx, key, scopes)
case msiTypeServiceFabric:
return c.createServiceFabricAuthRequest(ctx, clientID, scopes)
return c.createServiceFabricAuthRequest(ctx, id, scopes)
case msiTypeCloudShell:
return c.createCloudShellAuthRequest(ctx, clientID, scopes)
return c.createCloudShellAuthRequest(ctx, id, scopes)
default:
errorMsg := ""
switch c.msiType {
Expand All @@ -193,7 +193,7 @@ func (c *managedIdentityClient) createAuthRequest(ctx context.Context, clientID
}
}

func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id string, scopes []string) (*policy.Request, error) {
func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
Expand All @@ -202,16 +202,18 @@ func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id st
q := request.Raw().URL.Query()
q.Add("api-version", c.imdsAPIVersion)
q.Add("resource", strings.Join(scopes, " "))
if c.id == ResourceID {
q.Add(qpResID, id)
} else if id != "" {
q.Add(qpClientID, id)
if id != nil {
if id.idKind() == miResourceID {
q.Add(qpResID, id.String())
} else {
q.Add(qpClientID, id.String())
}
}
request.Raw().URL.RawQuery = q.Encode()
return request, nil
}

func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context, id string, scopes []string) (*policy.Request, error) {
func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
Expand All @@ -221,28 +223,32 @@ func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context,
request.Raw().Header.Set("secret", os.Getenv(msiSecret))
q.Add("api-version", "2017-09-01")
q.Add("resource", strings.Join(scopes, " "))
if c.id == ResourceID {
q.Add(qpResID, id)
} else if id != "" {
// the legacy 2017 API version specifically specifies "clientid" and not "client_id" as a query param
q.Add("clientid", id)
if id != nil {
if id.idKind() == miResourceID {
q.Add(qpResID, id.String())
} else {
// the legacy 2017 API version specifically specifies "clientid" and not "client_id" as a query param
q.Add("clientid", id.String())
}
}
} else if c.msiType == msiTypeAppServiceV20190801 {
request.Raw().Header.Set("X-IDENTITY-HEADER", os.Getenv(identityHeader))
q.Add("api-version", "2019-08-01")
q.Add("resource", scopes[0])
if c.id == ResourceID {
q.Add(qpResID, id)
} else if id != "" {
q.Add(qpClientID, id)
if id != nil {
if id.idKind() == miResourceID {
q.Add(qpResID, id.String())
} else {
q.Add(qpClientID, id.String())
}
}
}

request.Raw().URL.RawQuery = q.Encode()
return request, nil
}

func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Context, id string, scopes []string) (*policy.Request, error) {
func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
Expand All @@ -252,8 +258,8 @@ func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Conte
request.Raw().Header.Set("Secret", os.Getenv(identityHeader))
q.Add("api-version", serviceFabricAPIVersion)
q.Add("resource", strings.Join(scopes, " "))
if id != "" {
q.Add(qpClientID, id)
if id != nil {
q.Add(qpClientID, id.String())
}
request.Raw().URL.RawQuery = q.Encode()
return request, nil
Expand Down Expand Up @@ -310,16 +316,16 @@ func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, k
return request, nil
}

func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context, clientID string, scopes []string) (*policy.Request, error) {
func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodPost, c.endpoint)
if err != nil {
return nil, err
}
request.Raw().Header.Set(headerMetadata, "true")
data := url.Values{}
data.Set("resource", strings.Join(scopes, " "))
if clientID != "" {
data.Set(qpClientID, clientID)
if id != nil {
data.Set(qpClientID, id.String())
}
dataEncoded := data.Encode()
body := streaming.NopCloser(strings.NewReader(dataEncoded))
Expand Down
74 changes: 52 additions & 22 deletions sdk/azidentity/managed_identity_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,58 @@ package azidentity

import (
"context"
"fmt"
"os"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
)

// ManagedIdentityIDKind is used to specify the type of identifier that is passed in for a user-assigned managed identity.
type ManagedIdentityIDKind int
type managedIdentityIDKind int

const (
// ClientID is the default identifier for a user-assigned managed identity.
ClientID ManagedIdentityIDKind = 0
// ResourceID is set when the resource ID of the user-assigned managed identity is to be used.
ResourceID ManagedIdentityIDKind = 1
miClientID managedIdentityIDKind = 0
miResourceID managedIdentityIDKind = 1
)

// ManagedIDKind identifies the ID of a managed identity as either a client or resource ID
type ManagedIDKind interface {
fmt.Stringer
idKind() managedIdentityIDKind
}

// ClientID is an identity's client ID. Use it with ManagedIdentityCredentialOptions, for example:
// ManagedIdentityCredentialOptions{ID: ClientID("7cf7db0d-...")}
type ClientID string

func (ClientID) idKind() managedIdentityIDKind {
return miClientID
}

func (c ClientID) String() string {
return string(c)
}

// ResourceID is an identity's resource ID. Use it with ManagedIdentityCredentialOptions, for example:
// ManagedIdentityCredentialOptions{ID: ResourceID("/subscriptions/...")}
type ResourceID string

func (ResourceID) idKind() managedIdentityIDKind {
return miResourceID
}

func (r ResourceID) String() string {
return string(r)
}

// ManagedIdentityCredentialOptions contains parameters that can be used to configure the pipeline used with Managed Identity Credential.
// All zero-value fields will be initialized with their default values.
type ManagedIdentityCredentialOptions struct {
// ID is used to configure an alternate identifier for a user-assigned identity. The default is client ID.
// Select the identifier to be used and pass the corresponding ID value in the string param in
// NewManagedIdentityCredential().
// Hint: Choose from the list of allowed ManagedIdentityIDKind values.
ID ManagedIdentityIDKind
// ID is the ID of a managed identity the credential should authenticate. Set this field to use a specific identity
// instead of the hosting environment's default. The value may be the identity's client ID or resource ID, but note that
// some platforms don't accept resource IDs.
ID ManagedIDKind

// HTTPClient sets the transport for making HTTP requests.
// Leave this as nil to use the default HTTP transport.
Expand All @@ -46,17 +73,15 @@ type ManagedIdentityCredentialOptions struct {
// managed identity environments such as Azure VMs, App Service, Azure Functions, Azure CloudShell, among others. More information about configuring managed identities can be found here:
// https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
type ManagedIdentityCredential struct {
id string
id ManagedIDKind
client *managedIdentityClient
}

// NewManagedIdentityCredential creates an instance of the ManagedIdentityCredential capable of authenticating a resource that has a managed identity.
// id: The ID that corresponds to the user assigned managed identity. Defaults to the identity's client ID. To use another identifier,
// pass in the value for the identifier here AND choose the correct ID kind to be used in the request by setting ManagedIdentityIDKind in the options.
// NewManagedIdentityCredential creates a credential instance capable of authenticating an Azure managed identity in any hosting environment
// supporting managed identities. See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview for more
// information about Azure Managed Identity.
// options: ManagedIdentityCredentialOptions that configure the pipeline for requests sent to Azure Active Directory.
// More information on user assigned managed identities cam be found here:
// https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview#how-a-user-assigned-managed-identity-works-with-an-azure-vm
func NewManagedIdentityCredential(id string, options *ManagedIdentityCredentialOptions) (*ManagedIdentityCredential, error) {
func NewManagedIdentityCredential(options *ManagedIdentityCredentialOptions) (*ManagedIdentityCredential, error) {
// Create a new Managed Identity Client with default options
if options == nil {
options = &ManagedIdentityCredentialOptions{}
Expand All @@ -72,11 +97,16 @@ func NewManagedIdentityCredential(id string, options *ManagedIdentityCredentialO
// Assign the msiType discovered onto the client
client.msiType = msiType
// check if no clientID is specified then check if it exists in an environment variable
if len(id) == 0 {
if options.ID == ResourceID {
id = os.Getenv("AZURE_RESOURCE_ID")
id := options.ID
if id == nil {
cID := os.Getenv("AZURE_CLIENT_ID")
if cID != "" {
id = ClientID(cID)
} else {
id = os.Getenv("AZURE_CLIENT_ID")
rID := os.Getenv("AZURE_RESOURCE_ID")
if rID != "" {
id = ResourceID(rID)
}
}
}
return &ManagedIdentityCredential{id: id, client: client}, nil
Expand Down
Loading