Skip to content

Commit

Permalink
Merge pull request #14 from deploymenttheory/feature-scaffolding
Browse files Browse the repository at this point in the history
refactor for entraID auth workflow with client build options for all auth journey's. with proxy support
  • Loading branch information
ShocOne authored Jul 23, 2024
2 parents c67ab89 + b8c98d9 commit 5ba67c2
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 49 deletions.
78 changes: 70 additions & 8 deletions internal/provider/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,81 @@ import (
"context"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
azidentity "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/deploymenttheory/terraform-provider-microsoft365/internal/helpers"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// createCredential creates an Azure credential based on the provider configuration.
func createCredential(ctx context.Context, data M365ProviderModel, clientOptions policy.ClientOptions) (azcore.TokenCredential, error) {
// configureEntraIDClientOptions configures the client options for Entra ID
func configureEntraIDClientOptions(ctx context.Context, useProxy bool, proxyURL string, authorityURL string, telemetryOptout bool) (policy.ClientOptions, error) {
tflog.Debug(ctx, "Configuring Entra ID client options")

clientOptions := policy.ClientOptions{}

if useProxy && proxyURL != "" {
proxyURLParsed, err := url.Parse(proxyURL)
if err != nil {
return clientOptions, fmt.Errorf("failed to parse the provided proxy URL '%s': %s", proxyURL, err.Error())
}

authClient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURLParsed),
},
}

clientOptions.Transport = authClient
}

clientOptions.Cloud = cloud.Configuration{
ActiveDirectoryAuthorityHost: authorityURL,
}

if telemetryOptout {
clientOptions.Telemetry.Disabled = true
}

clientOptions.Logging = policy.LogOptions{
IncludeBody: true,
AllowedHeaders: []string{
"Content-Type",
"Authorization",
},
AllowedQueryParams: []string{
"api-version",
},
}

clientOptions.Retry = policy.RetryOptions{
MaxRetries: 5,
RetryDelay: 2 * time.Second,
MaxRetryDelay: 30 * time.Second,
StatusCodes: []int{
http.StatusRequestTimeout,
http.StatusTooManyRequests,
http.StatusInternalServerError,
http.StatusBadGateway,
http.StatusServiceUnavailable,
http.StatusGatewayTimeout,
},
}
tflog.Debug(ctx, "Configured Entra ID client options")

return clientOptions, nil
}

// obtainCredential creates an Azure credential based on the provider configuration.
func obtainCredential(ctx context.Context, data M365ProviderModel, clientOptions policy.ClientOptions) (azcore.TokenCredential, error) {
switch data.AuthMethod.ValueString() {
case "device_code":
tflog.Debug(ctx, "Creating DeviceCodeCredential", map[string]interface{}{
tflog.Debug(ctx, "Obtaining Device Code Credential", map[string]interface{}{
"tenant_id": data.TenantID.ValueString(),
"client_id": data.ClientID.ValueString(),
})
Expand All @@ -30,15 +92,15 @@ func createCredential(ctx context.Context, data M365ProviderModel, clientOptions
ClientOptions: clientOptions,
})
case "client_secret":
tflog.Debug(ctx, "Creating ClientSecretCredential", map[string]interface{}{
tflog.Debug(ctx, "Obtaining Client Secret Credential", map[string]interface{}{
"tenant_id": data.TenantID.ValueString(),
"client_id": data.ClientID.ValueString(),
})
return azidentity.NewClientSecretCredential(data.TenantID.ValueString(), data.ClientID.ValueString(), data.ClientSecret.ValueString(), &azidentity.ClientSecretCredentialOptions{
ClientOptions: clientOptions,
})
case "client_certificate":
tflog.Debug(ctx, "Creating ClientCertificateCredential", map[string]interface{}{
tflog.Debug(ctx, "Obtaining Client Certificate Credential", map[string]interface{}{
"tenant_id": data.TenantID.ValueString(),
"client_id": data.ClientID.ValueString(),
})
Expand All @@ -65,7 +127,7 @@ func createCredential(ctx context.Context, data M365ProviderModel, clientOptions
ClientOptions: clientOptions,
})
case "on_behalf_of":
tflog.Debug(ctx, "Creating OnBehalfOfCredentialWithSecret", map[string]interface{}{
tflog.Debug(ctx, "Obtaining OnBehalfOf Credential With Secret", map[string]interface{}{
"tenant_id": data.TenantID.ValueString(),
"client_id": data.ClientID.ValueString(),
})
Expand All @@ -74,7 +136,7 @@ func createCredential(ctx context.Context, data M365ProviderModel, clientOptions
ClientOptions: clientOptions,
})
case "interactive_browser":
tflog.Debug(ctx, "Creating InteractiveBrowserCredential", map[string]interface{}{
tflog.Debug(ctx, "Obtaining Interactive Browser Credential", map[string]interface{}{
"tenant_id": data.TenantID.ValueString(),
"client_id": data.ClientID.ValueString(),
"redirect_url": data.RedirectURL.ValueString(),
Expand All @@ -87,7 +149,7 @@ func createCredential(ctx context.Context, data M365ProviderModel, clientOptions
ClientOptions: clientOptions,
})
case "username_password":
tflog.Debug(ctx, "Creating UsernamePasswordCredential", map[string]interface{}{
tflog.Debug(ctx, "Obtaining Username / Password Credential", map[string]interface{}{
"tenant_id": data.TenantID.ValueString(),
"client_id": data.ClientID.ValueString(),
})
Expand Down
51 changes: 10 additions & 41 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"regexp"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/deploymenttheory/terraform-provider-microsoft365/internal/helpers"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
Expand Down Expand Up @@ -290,6 +286,7 @@ func (p *M365Provider) Configure(ctx context.Context, req provider.ConfigureRequ
nationalCloudDeployment := helpers.GetEnvOrDefaultBool(data.NationalCloudDeployment.ValueBool(), "M365_NATIONAL_CLOUD_DEPLOYMENT")
nationalCloudDeploymentTokenEndpoint := helpers.GetEnvOrDefault(data.NationalCloudDeploymentTokenEndpoint.ValueString(), "M365_NATIONAL_CLOUD_DEPLOYMENT_TOKEN_ENDPOINT")
nationalCloudDeploymentServiceEndpointRoot := helpers.GetEnvOrDefault(data.NationalCloudDeploymentServiceEndpointRoot.ValueString(), "M365_NATIONAL_CLOUD_DEPLOYMENT_SERVICE_ENDPOINT_ROOT")
telemetryOptout := helpers.GetEnvOrDefaultBool(data.TelemetryOptout.ValueBool(), "M365_TELEMETRY_OPTOUT")

ctx = tflog.SetField(ctx, "auth_method", authMethod)
ctx = tflog.SetField(ctx, "use_graph_beta", useGraphBeta)
Expand Down Expand Up @@ -323,11 +320,10 @@ func (p *M365Provider) Configure(ctx context.Context, req provider.ConfigureRequ
ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "client_id")
ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "client_secret")

// set cloud-specific constants
authorityURL, apiScope, err := setCloudConstants(cloud)
if err != nil {
resp.Diagnostics.AddError(
"Invalid Cloud Type",
"Invalid Microsoft Cloud Type",
fmt.Sprintf("An error occurred while attempting to get cloud constants for cloud type '%s'. "+
"Please ensure the cloud type is valid. Detailed error: %s", cloud, err.Error()),
)
Expand All @@ -337,43 +333,16 @@ func (p *M365Provider) Configure(ctx context.Context, req provider.ConfigureRequ
ctx = tflog.SetField(ctx, "authority_url", authorityURL)
ctx = tflog.SetField(ctx, "api_scope", apiScope)

var cred azcore.TokenCredential

if data.Token.IsUnknown() || data.Token.IsNull() {
token := os.Getenv("M365_API_TOKEN")
if token != "" {
data.Token = types.StringValue(token)
} else {
resp.Diagnostics.AddWarning(
"M365 Provider Configuration Warning",
"The API token is not set in the provider configuration and the environment variable 'M365_API_TOKEN' is empty. "+
"The provider will attempt to obtain a token dynamically using the provided credentials from Entra ID.",
)
}
}
// optionally set proxy
clientOptions := policy.ClientOptions{}
if useProxy && proxyURL != "" {
proxyURLParsed, err := url.Parse(proxyURL)
if err != nil {
resp.Diagnostics.AddError(
"Invalid Proxy URL",
fmt.Sprintf("Failed to parse the provided proxy URL '%s': %s. "+
"Ensure the URL is correctly formatted.", proxyURL, err.Error()),
)
return
}

authClient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURLParsed),
},
}

clientOptions.Transport = authClient
clientOptions, err := configureEntraIDClientOptions(ctx, useProxy, proxyURL, authorityURL, telemetryOptout)
if err != nil {
resp.Diagnostics.AddError(
"Unable to configure client options",
fmt.Sprintf("An error occurred while attempting to configure client options. Detailed error: %s", err.Error()),
)
return
}

cred, err = createCredential(ctx, data, clientOptions)
cred, err := obtainCredential(ctx, data, clientOptions)
if err != nil {
resp.Diagnostics.AddError(
"Unable to create credentials",
Expand Down

0 comments on commit 5ba67c2

Please sign in to comment.