Skip to content

Commit

Permalink
Merge pull request #574 from terraform-providers/provider-block
Browse files Browse the repository at this point in the history
Conditional loading of the Subscription ID / Tenant ID / Environment
  • Loading branch information
tombuildsstuff authored Dec 6, 2017
2 parents 9a416b5 + f5a01b1 commit 1005762
Show file tree
Hide file tree
Showing 15 changed files with 1,189 additions and 174 deletions.
5 changes: 3 additions & 2 deletions azurerm/azurerm_sweeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/authentication"
)

func TestMain(m *testing.M) {
Expand All @@ -29,7 +30,7 @@ func buildConfigForSweepers() (*ArmClient, error) {
return nil, fmt.Errorf("ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET and ARM_TENANT_ID must be set for acceptance tests")
}

config := &Config{
config := &authentication.Config{
SubscriptionID: subscriptionID,
ClientID: clientID,
ClientSecret: clientSecret,
Expand All @@ -38,7 +39,7 @@ func buildConfigForSweepers() (*ArmClient, error) {
SkipProviderRegistration: false,
}

return config.getArmClient()
return getArmClient(config)
}

func shouldSweepAcceptanceTestResource(name string, resourceLocation string, region string) bool {
Expand Down
11 changes: 6 additions & 5 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/terraform/terraform"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/authentication"
)

// ArmClient contains the handles to all the specific Azure Resource Manager
Expand Down Expand Up @@ -213,7 +214,7 @@ func setUserAgent(client *autorest.Client) {
}
}

func (c *Config) getAuthorizationToken(oauthConfig *adal.OAuthConfig, endpoint string) (*autorest.BearerAuthorizer, error) {
func getAuthorizationToken(c *authentication.Config, oauthConfig *adal.OAuthConfig, endpoint string) (*autorest.BearerAuthorizer, error) {
useServicePrincipal := c.ClientSecret != ""

if useServicePrincipal {
Expand Down Expand Up @@ -245,7 +246,7 @@ func (c *Config) getAuthorizationToken(oauthConfig *adal.OAuthConfig, endpoint s

// getArmClient is a helper method which returns a fully instantiated
// *ArmClient based on the Config's current settings.
func (c *Config) getArmClient() (*ArmClient, error) {
func getArmClient(c *authentication.Config) (*ArmClient, error) {
// detect cloud from environment
env, envErr := azure.EnvironmentFromName(c.Environment)
if envErr != nil {
Expand Down Expand Up @@ -280,21 +281,21 @@ func (c *Config) getArmClient() (*ArmClient, error) {

// Resource Manager endpoints
endpoint := env.ResourceManagerEndpoint
auth, err := c.getAuthorizationToken(oauthConfig, endpoint)
auth, err := getAuthorizationToken(c, oauthConfig, endpoint)
if err != nil {
return nil, err
}

// Graph Endpoints
graphEndpoint := env.GraphEndpoint
graphAuth, err := c.getAuthorizationToken(oauthConfig, graphEndpoint)
graphAuth, err := getAuthorizationToken(c, oauthConfig, graphEndpoint)
if err != nil {
return nil, err
}

// Key Vault Endpoints
keyVaultAuth := autorest.NewBearerAuthorizerCallback(sender, func(tenantID, resource string) (*autorest.BearerAuthorizer, error) {
keyVaultSpt, err := c.getAuthorizationToken(oauthConfig, resource)
keyVaultSpt, err := getAuthorizationToken(c, oauthConfig, resource)
if err != nil {
return nil, err
}
Expand Down
55 changes: 55 additions & 0 deletions azurerm/helpers/authentication/access_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package authentication

import (
"fmt"
"log"
"strings"
"time"

"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure/cli"
)

type AccessToken struct {
ClientID string
AccessToken *adal.Token
IsCloudShell bool
}

func findValidAccessTokenForTenant(tokens []cli.Token, tenantId string) (*AccessToken, error) {
for _, accessToken := range tokens {
token, err := accessToken.ToADALToken()
if err != nil {
return nil, fmt.Errorf("[DEBUG] Error converting access token to token: %+v", err)
}

expirationDate, err := cli.ParseExpirationDate(accessToken.ExpiresOn)
if err != nil {
return nil, fmt.Errorf("Error parsing expiration date: %q", accessToken.ExpiresOn)
}

if expirationDate.UTC().Before(time.Now().UTC()) {
log.Printf("[DEBUG] Token %q has expired", token.AccessToken)
continue
}

if !strings.Contains(accessToken.Resource, "management") {
log.Printf("[DEBUG] Resource %q isn't a management domain", accessToken.Resource)
continue
}

if !strings.HasSuffix(accessToken.Authority, tenantId) {
log.Printf("[DEBUG] Resource %q isn't for the correct Tenant", accessToken.Resource)
continue
}

validAccessToken := AccessToken{
ClientID: accessToken.ClientID,
AccessToken: &token,
IsCloudShell: accessToken.RefreshToken == "",
}
return &validAccessToken, nil
}

return nil, fmt.Errorf("No Access Token was found for the Tenant ID %q", tenantId)
}
217 changes: 217 additions & 0 deletions azurerm/helpers/authentication/access_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package authentication

import (
"testing"
"time"

"github.com/Azure/go-autorest/autorest/azure/cli"
)

func TestAzureFindValidAccessTokenForTenant_InvalidDate(t *testing.T) {
tenantId := "c056adac-c6a6-4ddf-ab20-0f26d47f7eea"
expectedToken := cli.Token{
ExpiresOn: "invalid date",
AccessToken: "7cabcf30-8dca-43f9-91e6-fd56dfb8632f",
TokenType: "9b10b986-7a61-4542-8d5a-9fcd96112585",
RefreshToken: "4ec3874d-ee2e-4980-ba47-b5bac11ddb94",
Resource: "https://management.core.windows.net/",
Authority: tenantId,
}
tokens := []cli.Token{expectedToken}
token, err := findValidAccessTokenForTenant(tokens, tenantId)

if err == nil {
t.Fatalf("Expected an error to be returned but got nil")
}

if token != nil {
t.Fatalf("Expected Token to be nil but got: %+v", token)
}
}

func TestAzureFindValidAccessTokenForTenant_Expired(t *testing.T) {
expirationDate := time.Now().Add(time.Minute * -1)
tenantId := "c056adac-c6a6-4ddf-ab20-0f26d47f7eea"
expectedToken := cli.Token{
ExpiresOn: expirationDate.Format("2006-01-02 15:04:05.999999"),
AccessToken: "7cabcf30-8dca-43f9-91e6-fd56dfb8632f",
TokenType: "9b10b986-7a61-4542-8d5a-9fcd96112585",
RefreshToken: "4ec3874d-ee2e-4980-ba47-b5bac11ddb94",
Resource: "https://management.core.windows.net/",
Authority: tenantId,
}
tokens := []cli.Token{expectedToken}
token, err := findValidAccessTokenForTenant(tokens, tenantId)

if err == nil {
t.Fatalf("Expected an error to be returned but got nil")
}

if token != nil {
t.Fatalf("Expected Token to be nil but got: %+v", token)
}
}

func TestAzureFindValidAccessTokenForTenant_ExpiringIn(t *testing.T) {
minutesToVerify := []int{1, 30, 60}

for _, minute := range minutesToVerify {
expirationDate := time.Now().Add(time.Minute * time.Duration(minute))
tenantId := "c056adac-c6a6-4ddf-ab20-0f26d47f7eea"
expectedToken := cli.Token{
ExpiresOn: expirationDate.Format("2006-01-02 15:04:05.999999"),
AccessToken: "7cabcf30-8dca-43f9-91e6-fd56dfb8632f",
TokenType: "9b10b986-7a61-4542-8d5a-9fcd96112585",
RefreshToken: "4ec3874d-ee2e-4980-ba47-b5bac11ddb94",
Resource: "https://management.core.windows.net/",
Authority: tenantId,
}
tokens := []cli.Token{expectedToken}
token, err := findValidAccessTokenForTenant(tokens, tenantId)

if err != nil {
t.Fatalf("Expected no error to be returned for minute %d but got %+v", minute, err)
}

if token == nil {
t.Fatalf("Expected Token to have a value for minute %d but it was nil", minute)
}

if token.AccessToken.AccessToken != expectedToken.AccessToken {
t.Fatalf("Expected the Access Token to be %q for minute %d but got %q", expectedToken.AccessToken, minute, token.AccessToken.AccessToken)
}

if token.ClientID != expectedToken.ClientID {
t.Fatalf("Expected the Client ID to be %q for minute %d but got %q", expectedToken.ClientID, minute, token.ClientID)
}

if token.IsCloudShell != false {
t.Fatalf("Expected `IsCloudShell` to be false for minute %d but got true", minute)
}
}
}

func TestAzureFindValidAccessTokenForTenant_InvalidManagementDomain(t *testing.T) {
expirationDate := time.Now().Add(1 * time.Hour)
tenantId := "c056adac-c6a6-4ddf-ab20-0f26d47f7eea"
expectedToken := cli.Token{
ExpiresOn: expirationDate.Format("2006-01-02 15:04:05.999999"),
AccessToken: "7cabcf30-8dca-43f9-91e6-fd56dfb8632f",
TokenType: "9b10b986-7a61-4542-8d5a-9fcd96112585",
Resource: "https://portal.azure.com/",
Authority: tenantId,
}
tokens := []cli.Token{expectedToken}
token, err := findValidAccessTokenForTenant(tokens, tenantId)

if err == nil {
t.Fatalf("Expected an error but didn't get one")
}

if token != nil {
t.Fatalf("Expected Token to be nil but got: %+v", token)
}
}

func TestAzureFindValidAccessTokenForTenant_DifferentTenant(t *testing.T) {
expirationDate := time.Now().Add(1 * time.Hour)
expectedToken := cli.Token{
ExpiresOn: expirationDate.Format("2006-01-02 15:04:05.999999"),
AccessToken: "7cabcf30-8dca-43f9-91e6-fd56dfb8632f",
TokenType: "9b10b986-7a61-4542-8d5a-9fcd96112585",
Resource: "https://management.core.windows.net/",
Authority: "9b5095de-5496-4b5e-9bc6-ef2c017b9d35",
}
tokens := []cli.Token{expectedToken}
token, err := findValidAccessTokenForTenant(tokens, "c056adac-c6a6-4ddf-ab20-0f26d47f7eea")

if err == nil {
t.Fatalf("Expected an error but didn't get one")
}

if token != nil {
t.Fatalf("Expected Token to be nil but got: %+v", token)
}
}

func TestAzureFindValidAccessTokenForTenant_ValidFromCloudShell(t *testing.T) {
expirationDate := time.Now().Add(1 * time.Hour)
tenantId := "c056adac-c6a6-4ddf-ab20-0f26d47f7eea"
expectedToken := cli.Token{
ExpiresOn: expirationDate.Format(time.RFC3339),
AccessToken: "7cabcf30-8dca-43f9-91e6-fd56dfb8632f",
TokenType: "9b10b986-7a61-4542-8d5a-9fcd96112585",
Resource: "https://management.core.windows.net/",
Authority: tenantId,
}
tokens := []cli.Token{expectedToken}
token, err := findValidAccessTokenForTenant(tokens, tenantId)

if err != nil {
t.Fatalf("Expected no error to be returned but got %+v", err)
}

if token == nil {
t.Fatalf("Expected Token to have a value but it was nil")
}

if token.AccessToken.AccessToken != expectedToken.AccessToken {
t.Fatalf("Expected the Access Token to be %q but got %q", expectedToken.AccessToken, token.AccessToken.AccessToken)
}

if token.ClientID != expectedToken.ClientID {
t.Fatalf("Expected the Client ID to be %q but got %q", expectedToken.ClientID, token.ClientID)
}

if token.IsCloudShell != true {
t.Fatalf("Expected `IsCloudShell` to be true but got false")
}
}

func TestAzureFindValidAccessTokenForTenant_ValidFromAzureCLI(t *testing.T) {
expirationDate := time.Now().Add(1 * time.Hour)
tenantId := "c056adac-c6a6-4ddf-ab20-0f26d47f7eea"
expectedToken := cli.Token{
ExpiresOn: expirationDate.Format("2006-01-02 15:04:05.999999"),
AccessToken: "7cabcf30-8dca-43f9-91e6-fd56dfb8632f",
TokenType: "9b10b986-7a61-4542-8d5a-9fcd96112585",
RefreshToken: "4ec3874d-ee2e-4980-ba47-b5bac11ddb94",
Resource: "https://management.core.windows.net/",
Authority: tenantId,
}
tokens := []cli.Token{expectedToken}
token, err := findValidAccessTokenForTenant(tokens, tenantId)

if err != nil {
t.Fatalf("Expected no error to be returned but got %+v", err)
}

if token == nil {
t.Fatalf("Expected Token to have a value but it was nil")
}

if token.AccessToken.AccessToken != expectedToken.AccessToken {
t.Fatalf("Expected the Access Token to be %q but got %q", expectedToken.AccessToken, token.AccessToken.AccessToken)
}

if token.ClientID != expectedToken.ClientID {
t.Fatalf("Expected the Client ID to be %q but got %q", expectedToken.ClientID, token.ClientID)
}

if token.IsCloudShell != false {
t.Fatalf("Expected `IsCloudShell` to be false but got true")
}
}

func TestAzureFindValidAccessTokenForTenant_NoTokens(t *testing.T) {
tokens := make([]cli.Token, 0)
token, err := findValidAccessTokenForTenant(tokens, "abc123")

if err == nil {
t.Fatalf("Expected an error but didn't get one")
}

if token != nil {
t.Fatalf("Expected a null token to be returned but got: %+v", token)
}
}
33 changes: 33 additions & 0 deletions azurerm/helpers/authentication/azure_cli_profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package authentication

import (
"strings"

"fmt"

"github.com/Azure/go-autorest/autorest/azure/cli"
)

type AzureCLIProfile struct {
cli.Profile
}

func (a AzureCLIProfile) FindDefaultSubscriptionId() (string, error) {
for _, subscription := range a.Subscriptions {
if subscription.IsDefault {
return subscription.ID, nil
}
}

return "", fmt.Errorf("No Subscription was Marked as Default in the Azure Profile.")
}

func (a AzureCLIProfile) FindSubscription(subscriptionId string) (*cli.Subscription, error) {
for _, subscription := range a.Subscriptions {
if strings.EqualFold(subscription.ID, subscriptionId) {
return &subscription, nil
}
}

return nil, fmt.Errorf("Subscription %q was not found in your Azure CLI credentials. Please verify it exists in `az account list`.", subscriptionId)
}
Loading

0 comments on commit 1005762

Please sign in to comment.