Skip to content

Commit

Permalink
Move NewManagedIdentityCredential's id parameter into ManagedIdentity…
Browse files Browse the repository at this point in the history
…CredentialOptions (#15637)
  • Loading branch information
chlowell authored Oct 4, 2021
1 parent 574854e commit 2492a9b
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 93 deletions.
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

0 comments on commit 2492a9b

Please sign in to comment.