Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fix: support missing oid claim #20465

Merged
merged 4 commits into from
Feb 16, 2023
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.1.2
github.com/hashicorp/go-azure-helpers v0.51.0
github.com/hashicorp/go-azure-sdk v0.20230215.1153645
github.com/hashicorp/go-azure-sdk v0.20230216.1112535
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
github.com/hashicorp/go-azure-helpers v0.12.0/go.mod h1:Zc3v4DNeX6PDdy7NljlYpnrdac1++qNW0I4U+ofGwpg=
github.com/hashicorp/go-azure-helpers v0.51.0 h1:8KSDGkGnWH6zOT60R3KUqsi0fk1vA7AMunaOUJZMM6k=
github.com/hashicorp/go-azure-helpers v0.51.0/go.mod h1:lsykLR4KjTUO7MiRmNWiTiX8QQtw3ILjyOvT0f5h3rw=
github.com/hashicorp/go-azure-sdk v0.20230215.1153645 h1:Sh5Zw8xBPKnD4A4X0ZQ00B9zk+grCsSvzLo6WJfB0kc=
github.com/hashicorp/go-azure-sdk v0.20230215.1153645/go.mod h1:aHinadEuBi04I1i+yvpPMZUxvxRxl5JgBOwlzIIxozU=
github.com/hashicorp/go-azure-sdk v0.20230216.1112535 h1:hlMwbjtj27LPUvITk9yZ3sJ3+wFalFIz6FQ0XoQQCD8=
github.com/hashicorp/go-azure-sdk v0.20230216.1112535/go.mod h1:aHinadEuBi04I1i+yvpPMZUxvxRxl5JgBOwlzIIxozU=
github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU=
github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
Expand Down
50 changes: 45 additions & 5 deletions internal/clients/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package clients
import (
"context"
"fmt"
"log"
"net/http"
"strings"

"github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/go-azure-sdk/sdk/auth"
"github.com/hashicorp/go-azure-sdk/sdk/claims"
"github.com/hashicorp/go-azure-sdk/sdk/environments"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients/graph"
)

type ResourceManagerAccount struct {
Expand All @@ -27,7 +29,12 @@ type ResourceManagerAccount struct {
AzureEnvironment azure.Environment
}

func NewResourceManagerAccount(ctx context.Context, authorizer auth.Authorizer, config auth.Credentials, subscriptionId string, skipResourceProviderRegistration bool, azureEnvironment azure.Environment) (*ResourceManagerAccount, error) {
func NewResourceManagerAccount(ctx context.Context, config auth.Credentials, subscriptionId string, skipResourceProviderRegistration bool, azureEnvironment azure.Environment) (*ResourceManagerAccount, error) {
authorizer, err := auth.NewAuthorizerFromCredentials(ctx, config, config.Environment.MicrosoftGraph)
if err != nil {
return nil, fmt.Errorf("unable to build authorizer for Microsoft Graph API: %+v", err)
}

// Acquire an access token so we can inspect the claims
token, err := authorizer.Token(ctx, &http.Request{})
if err != nil {
Expand All @@ -40,17 +47,50 @@ func NewResourceManagerAccount(ctx context.Context, authorizer auth.Authorizer,
}

authenticatedAsServicePrincipal := true
if strings.Contains(strings.ToLower(claims.Scopes), "user_impersonation") {
if strings.Contains(strings.ToLower(claims.Scopes), "openid") {
authenticatedAsServicePrincipal = false
}

clientId := claims.AppId
if clientId == "" {
log.Printf("[DEBUG] Using user-supplied ClientID because the `appid` claim was missing from the access token")
clientId = config.ClientID
manicminer marked this conversation as resolved.
Show resolved Hide resolved
}

objectId := claims.ObjectId
if objectId == "" {
if authenticatedAsServicePrincipal {
manicminer marked this conversation as resolved.
Show resolved Hide resolved
log.Printf("[DEBUG] Querying Microsoft Graph to discover authenticated service principal object ID because the `oid` claim was missing from the access token")
id, err := graph.ServicePrincipalObjectID(ctx, authorizer, config.Environment, config.ClientID)
if err != nil {
return nil, fmt.Errorf("attempting to discover object ID for authenticated service principal with client ID %q: %+v", config.ClientID, err)
}

objectId = *id
} else {
log.Printf("[DEBUG] Querying Microsoft Graph to discover authenticated user principal object ID because the `oid` claim was missing from the access token")
id, err := graph.UserPrincipalObjectID(ctx, authorizer, config.Environment)
if err != nil {
return nil, fmt.Errorf("attempting to discover object ID for authenticated user principal: %+v", err)
}

objectId = *id
}
}

tenantId := claims.TenantId
if tenantId == "" {
log.Printf("[DEBUG] Using user-supplied TenantID because the `tid` claim was missing from the access token")
tenantId = config.TenantID
manicminer marked this conversation as resolved.
Show resolved Hide resolved
}

account := ResourceManagerAccount{
Environment: config.Environment,

ClientId: claims.AppId,
ObjectId: claims.ObjectId,
ClientId: clientId,
ObjectId: objectId,
SubscriptionId: subscriptionId,
TenantId: claims.TenantId,
TenantId: tenantId,

AuthenticatedAsAServicePrincipal: authenticatedAsServicePrincipal,
SkipResourceProviderRegistration: skipResourceProviderRegistration,
Expand Down
2 changes: 1 addition & 1 deletion internal/clients/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func Build(ctx context.Context, builder ClientBuilder) (*Client, error) {
}
resourceManagerEndpoint, _ := builder.AuthConfig.Environment.ResourceManager.Endpoint()

account, err := NewResourceManagerAccount(ctx, resourceManagerAuth, *builder.AuthConfig, builder.SubscriptionID, builder.SkipProviderRegistration, *azureEnvironment)
account, err := NewResourceManagerAccount(ctx, *builder.AuthConfig, builder.SubscriptionID, builder.SkipProviderRegistration, *azureEnvironment)
if err != nil {
return nil, fmt.Errorf("building account: %+v", err)
}
Expand Down
148 changes: 148 additions & 0 deletions internal/clients/graph/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package graph

import (
"context"
"fmt"
"net/http"
"time"

"github.com/hashicorp/go-azure-sdk/sdk/auth"
"github.com/hashicorp/go-azure-sdk/sdk/client"
"github.com/hashicorp/go-azure-sdk/sdk/client/msgraph"
"github.com/hashicorp/go-azure-sdk/sdk/environments"
"github.com/hashicorp/go-azure-sdk/sdk/odata"
)

type options struct {
query odata.Query
}

func (o options) ToHeaders() *client.Headers {
h := client.Headers{}
h.AppendHeader(o.query.Headers())
return &h
}

func (o options) ToOData() *odata.Query {
return &o.query
}

func (o options) ToQuery() *client.QueryParams {
q := client.QueryParams{}
q.AppendValues(o.query.Values())
return &q
}

type directoryObjectModel struct {
ID *string `json:"id"`
}

func graphClient(authorizer auth.Authorizer, environment environments.Environment) (*msgraph.Client, error) {
client, err := msgraph.NewMsGraphClient(environment.MicrosoftGraph, msgraph.VersionOnePointZero)
if err != nil {
return nil, fmt.Errorf("building client: %+v", err)
}

client.Authorizer = authorizer

return client, nil
}

func ServicePrincipalObjectID(ctx context.Context, authorizer auth.Authorizer, environment environments.Environment, clientId string) (*string, error) {
if _, ok := ctx.Deadline(); !ok {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, time.Now().Add(5*time.Minute))
defer cancel()
}

opts := client.RequestOptions{
ContentType: "application/json",
ExpectedStatusCodes: []int{
http.StatusOK,
},
HttpMethod: http.MethodGet,
OptionsObject: options{
query: odata.Query{
Filter: fmt.Sprintf("appId eq '%s'", clientId),
},
},
Path: "/servicePrincipals",
}

client, err := graphClient(authorizer, environment)
if err != nil {
return nil, err
}

req, err := client.NewRequest(ctx, opts)
if err != nil {
return nil, fmt.Errorf("building new request: %+v", err)
}

resp, err := req.Execute(ctx)
if err != nil {
return nil, fmt.Errorf("executing request: %+v", err)
}

model := struct {
ServicePrincipals []directoryObjectModel `json:"value"`
}{}
if err := resp.Unmarshal(&model); err != nil {
return nil, fmt.Errorf("unmarshaling response: %+v", err)
}

if len(model.ServicePrincipals) != 1 {
return nil, fmt.Errorf("unexpected number of results, expected 1, received %d", len(model.ServicePrincipals))
}

id := model.ServicePrincipals[0].ID
if id == nil {
return nil, fmt.Errorf("returned object ID was nil")
}

return id, nil
}

func UserPrincipalObjectID(ctx context.Context, authorizer auth.Authorizer, environment environments.Environment) (*string, error) {
if _, ok := ctx.Deadline(); !ok {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, time.Now().Add(5*time.Minute))
defer cancel()
}

opts := client.RequestOptions{
ContentType: "application/json",
ExpectedStatusCodes: []int{
http.StatusOK,
},
HttpMethod: http.MethodGet,
OptionsObject: nil,
Path: "/me",
}

client, err := graphClient(authorizer, environment)
if err != nil {
return nil, err
}

req, err := client.NewRequest(ctx, opts)
if err != nil {
return nil, fmt.Errorf("building new request: %+v", err)
}

resp, err := req.Execute(ctx)
if err != nil {
return nil, fmt.Errorf("executing request: %+v", err)
}

model := directoryObjectModel{}
if err := resp.Unmarshal(&model); err != nil {
return nil, fmt.Errorf("unmarshaling response: %+v", err)
}

if model.ID == nil {
return nil, fmt.Errorf("returned object ID was nil")
}

return model.ID, nil
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading