Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ provider "msgraph" {
- `custom_correlation_request_id` (String) The value of the `x-ms-correlation-request-id` header, otherwise an auto-generated UUID will be used. This can also be sourced from the `ARM_CORRELATION_REQUEST_ID` environment variable.
- `disable_correlation_request_id` (Boolean) This will disable the x-ms-correlation-request-id header.
- `disable_terraform_partner_id` (Boolean) Disable sending the Terraform Partner ID if a custom `partner_id` isn't specified, which allows Microsoft to better understand the usage of Terraform. The Partner ID does not give HashiCorp any direct access to usage information. This can also be sourced from the `ARM_DISABLE_TERRAFORM_PARTNER_ID` environment variable. Defaults to `false`.
- `environment` (String) The cloud environment which should be used. Possible values are: `global` (also `public`), `usgovernmentl4` (also `usgovernment`), `usgovernmentl5` (also `dod`), and `china`. Defaults to `global`. This can also be sourced from the `ARM_ENVIRONMENT` environment variable.
- `oidc_azure_service_connection_id` (String) The Azure Pipelines Service Connection ID to use for authentication. This can also be sourced from the `ARM_OIDC_AZURE_SERVICE_CONNECTION_ID` environment variable.
- `oidc_request_token` (String) The bearer token for the request to the OIDC provider. This can also be sourced from the `ARM_OIDC_REQUEST_TOKEN` or `ACTIONS_ID_TOKEN_REQUEST_TOKEN` Environment Variables.
- `oidc_request_url` (String) The URL for the OIDC provider from which to request an ID token. This can also be sourced from the `ARM_OIDC_REQUEST_URL` or `ACTIONS_ID_TOKEN_REQUEST_URL` Environment Variables.
Expand Down
19 changes: 14 additions & 5 deletions internal/acceptance/testclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,23 @@ func BuildTestClient() (*clients.Client, error) {
if _client == nil {
var cloudConfig cloud.Configuration
env := os.Getenv("ARM_ENVIRONMENT")
var environment string
switch strings.ToLower(env) {
case "public":
case "public", "global", "":
cloudConfig = cloud.AzurePublic
case "usgovernment":
environment = "global"
case "usgovernment", "usgovernmentl4":
cloudConfig = cloud.AzureGovernment
environment = "usgovernmentl4"
case "usgovernmentl5", "dod":
cloudConfig = cloud.AzureGovernment
environment = "usgovernmentl5"
case "china":
cloudConfig = cloud.AzureChina
environment = "china"
default:
cloudConfig = cloud.AzurePublic
environment = "global"
}

model := provider.MSGraphProviderModel{}
Expand Down Expand Up @@ -117,9 +125,10 @@ func BuildTestClient() (*clients.Client, error) {
}

copt := &clients.Option{
Cred: cred,
CloudCfg: cloudConfig,
TenantId: os.Getenv("ARM_TENANT_ID"),
Cred: cred,
CloudCfg: cloudConfig,
TenantId: os.Getenv("ARM_TENANT_ID"),
Environment: environment,
}

client := &clients.Client{}
Expand Down
3 changes: 2 additions & 1 deletion internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Option struct {
CloudCfg cloud.Configuration
CustomCorrelationRequestID string
TenantId string
Environment string
}

func (client *Client) Build(ctx context.Context, o *Option) error {
Expand Down Expand Up @@ -86,7 +87,7 @@ func (client *Client) Build(ctx context.Context, o *Option) error {
"$format",
}

msgraphClient, err := NewMSGraphClient(o.Cred, &policy.ClientOptions{
msgraphClient, err := NewMSGraphClient(MSGraphEndpointForEnvironment(o.Environment), o.Cred, &policy.ClientOptions{
Logging: policy.LogOptions{
IncludeBody: false,
AllowedHeaders: allowedHeaders,
Expand Down
35 changes: 32 additions & 3 deletions internal/clients/msgraph_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,53 @@ const (
nextLinkKey = "@odata.nextLink"
)

const DefaultEnvironment = "global"

// EnvironmentEndpoints maps environment names to their Microsoft Graph endpoint URLs.
var EnvironmentEndpoints = map[string]string{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use cloud.Configuration as the single source of truth per environment

Replace the map[string]string endpoint map with a map[string]cloud.Configuration that embeds MS Graph as a ServiceConfiguration:

const MicrosoftGraph cloud.ServiceName = "microsoftGraph"

var Environments = map[string]cloud.Configuration{
    "global":         { AuthorityHost: "login.microsoftonline.com",  MicrosoftGraph: { Endpoint: "https://graph.microsoft.com",                Audience: "https://graph.microsoft.com" } },
    "usgovernmentl4": { AuthorityHost: "login.microsoftonline.us",   MicrosoftGraph: { Endpoint: "https://graph.microsoft.us",                 Audience: "https://graph.microsoft.us" } },
    "usgovernmentl5": { AuthorityHost: "login.microsoftonline.us",   MicrosoftGraph: { Endpoint: "https://dod-graph.microsoft.us",             Audience: "https://dod-graph.microsoft.us" } },
    "china":          { AuthorityHost: "login.chinacloudapi.cn",     MicrosoftGraph: { Endpoint: "https://microsoftgraph.chinacloudapi.cn",    Audience: "https://microsoftgraph.chinacloudapi.cn" } },
}

So it could remove the CloudCfg + Environment on clients.Option.

And use use explicit Audience + "/.default" instead of endpoint + "/.default".

"global": "https://graph.microsoft.com",
"public": "https://graph.microsoft.com",
"usgovernmentl4": "https://graph.microsoft.us",
"usgovernment": "https://graph.microsoft.us",
"usgovernmentl5": "https://dod-graph.microsoft.us",
"dod": "https://dod-graph.microsoft.us",
"china": "https://microsoftgraph.chinacloudapi.cn",
}

// MSGraphEndpointForEnvironment returns the Microsoft Graph endpoint URL for the given environment name.
func MSGraphEndpointForEnvironment(env string) string {
if endpoint, ok := EnvironmentEndpoints[env]; ok {
return endpoint
}
return EnvironmentEndpoints[DefaultEnvironment]
}

type MSGraphClient struct {
host string
pl runtime.Pipeline
}

func NewMSGraphClient(credential azcore.TokenCredential, opt *policy.ClientOptions) (*MSGraphClient, error) {
// Host returns the Microsoft Graph endpoint base URL (e.g. "https://graph.microsoft.com").
func (client *MSGraphClient) Host() string {
return client.host
}

func NewMSGraphClient(endpoint string, credential azcore.TokenCredential, opt *policy.ClientOptions) (*MSGraphClient, error) {
if endpoint == "" {
endpoint = EnvironmentEndpoints[DefaultEnvironment]
}
pl := runtime.NewPipeline(moduleName, moduleVersion, runtime.PipelineOptions{
AllowedHeaders: nil,
AllowedQueryParameters: nil,
APIVersion: runtime.APIVersionOptions{},
PerCall: nil,
PerRetry: []policy.Policy{
runtime.NewBearerTokenPolicy(credential, []string{"https://graph.microsoft.com/.default"}, nil),
runtime.NewBearerTokenPolicy(credential, []string{endpoint + "/.default"}, nil),
},
Tracing: runtime.TracingOptions{},
}, opt)
return &MSGraphClient{
host: "https://graph.microsoft.com",
host: endpoint,
pl: pl,
}, nil
}
Expand Down
42 changes: 42 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ import (

var _ provider.Provider = &MSGraphProvider{}

var validEnvironments = []string{
"global",
"public",
"usgovernmentl4",
"usgovernment",
"usgovernmentl5",
"dod",
"china",
}

type MSGraphProvider struct{}

type MSGraphProviderModel struct {
Expand All @@ -47,6 +57,7 @@ type MSGraphProviderModel struct {
UsePowerShell types.Bool `tfsdk:"use_powershell"`
UseMSI types.Bool `tfsdk:"use_msi"`
UseAKSWorkloadIdentity types.Bool `tfsdk:"use_aks_workload_identity"`
Environment types.String `tfsdk:"environment"`
PartnerID types.String `tfsdk:"partner_id"`
CustomCorrelationRequestID types.String `tfsdk:"custom_correlation_request_id"`
DisableCorrelationRequestID types.Bool `tfsdk:"disable_correlation_request_id"`
Expand Down Expand Up @@ -227,6 +238,15 @@ func (p *MSGraphProvider) Schema(ctx context.Context, req provider.SchemaRequest
MarkdownDescription: "Should AKS Workload Identity be used for Authentication? This can also be sourced from the `ARM_USE_AKS_WORKLOAD_IDENTITY` Environment Variable. Defaults to `false`. When set, `client_id`, `tenant_id` and `oidc_token_file_path` will be detected from the environment and do not need to be specified.",
},

// Cloud environment
"environment": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf(validEnvironments...),
},
MarkdownDescription: "The cloud environment which should be used. Possible values are: `global` (also `public`), `usgovernmentl4` (also `usgovernment`), `usgovernmentl5` (also `dod`), and `china`. Defaults to `global`. This can also be sourced from the `ARM_ENVIRONMENT` environment variable.",
},

// Managed Tracking GUID for User-agent
"partner_id": schema.StringAttribute{
Optional: true,
Expand Down Expand Up @@ -418,6 +438,27 @@ func (p *MSGraphProvider) Configure(ctx context.Context, req provider.ConfigureR
}
}

if model.Environment.IsNull() {
if v := os.Getenv("ARM_ENVIRONMENT"); v != "" {
v = strings.ToLower(v)
valid := false
for _, env := range validEnvironments {
if v == env {
valid = true
break
}
}
if !valid {
resp.Diagnostics.AddError("Invalid `environment` value",
fmt.Sprintf("The value %q provided via ARM_ENVIRONMENT is not a valid environment. Valid values are: global (also public), usgovernmentl4 (also usgovernment), usgovernmentl5 (also dod), china", v))
return
}
model.Environment = types.StringValue(v)
} else {
model.Environment = types.StringValue(clients.DefaultEnvironment)
}
}

option := azidentity.DefaultAzureCredentialOptions{
TenantID: model.TenantID.ValueString(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Cloud field on the credential options is not set based on the selected environment. This means all authentication flows (client secret, client certificate, OIDC, CLI, etc.) will still try to obtain tokens from login.microsoftonline.com even when targeting USGov or China clouds.

}
Expand All @@ -435,6 +476,7 @@ func (p *MSGraphProvider) Configure(ctx context.Context, req provider.ConfigureR
CustomCorrelationRequestID: model.CustomCorrelationRequestID.ValueString(),
CloudCfg: cloud.Configuration{},
TenantId: model.TenantID.ValueString(),
Environment: model.Environment.ValueString(),
}
client := &clients.Client{}
if err = client.Build(ctx, copt); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/services/msgraph_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ func (r *MSGraphResource) Read(ctx context.Context, req resource.ReadRequest, re

if v, _ := req.Private.GetKey(ctx, FlagMoveState); v != nil && string(v) == "true" {
body := map[string]string{
"@odata.id": fmt.Sprintf("https://graph.microsoft.com/v1.0/directoryObjects/%s", model.Id.ValueString()),
"@odata.id": fmt.Sprintf("%s/v1.0/directoryObjects/%s", r.client.Host(), model.Id.ValueString()),
}
data, err := json.Marshal(body)
if err != nil {
Expand Down