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

Conditional loading of the Subscription ID / Tenant ID / Environment #574

Merged
merged 3 commits into from
Dec 6, 2017
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
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