Skip to content

Commit

Permalink
Support for accounts based in China and GovCloud Partitions (#922)
Browse files Browse the repository at this point in the history
* The Toolkit can now Connect to AWS with accounts based in other partitions (verified with the 'aws-cn' and 'aws-us-gov' partitions) #188 
  * The `region` key in Shared Credential Profiles drives this behaviour; Credentials are assumed to be in 'aws' partition otherwise
  * AWS Explorer will only show regions related to that partition
  * Region Pickers (Examples: Deploy SAM Application, Show Region in AWS Explorer) will only show regions related to that partition
  * a quick explanation on how to set this up can be found in /docs/connecting-to-aws.md

Also:
* Updated the "Connecting to AWS" documentation, and added content related to supporting non-aws Partitions
* Removed older RegionInfo structure in favour of endpoints based Region structure
  • Loading branch information
awschristou authored Feb 18, 2020
1 parent 4da0ae3 commit c30b3c5
Show file tree
Hide file tree
Showing 20 changed files with 312 additions and 138 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Feature",
"description": "The Toolkit now supports China and GovCloud regions. If you have a shared credentials profile based in one of these regions, you can add a \"region\" property to that profile, and the Toolkit will know to use a different region set."
}
37 changes: 25 additions & 12 deletions docs/connecting-to-aws.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
# Connecting to AWS from the AWS Toolkit for Visual Studio Code

For full details on how to connect to AWS from the **AWS Toolkit for Visual Studio Code (VS Code)**, see the [user guide](https://docs.aws.amazon.com/console/toolkit-for-vscode/welcome), specifically the topics about [setting up your AWS credentials](https://docs.aws.amazon.com/console/toolkit-for-vscode/setup-credentials) and [connecting to AWS](https://docs.aws.amazon.com/console/toolkit-for-vscode/connect).
For full details on how to connect to AWS from the **AWS Toolkit for Visual Studio Code (VS Code)**, see the [user guide](https://docs.aws.amazon.com/console/toolkit-for-vscode/welcome), specifically the topics about [setting up your AWS credentials](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/setup-credentials.html) and [connecting to AWS](https://docs.aws.amazon.com/console/toolkit-for-vscode/connect).

The **AWS Toolkit for VS Code** uses the same credentials files as the [AWS Command Line Interface](https://aws.amazon.com/cli/). Read more about these files in the [Configuration and Credential Files](https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html) topic of the _AWS Command Line Interface User Guide_. Note that the AWS Command Line Interface is required only for certain features of the AWS Toolkit for VS Code.
The **AWS Toolkit for VS Code** uses the same shared credentials files as the [AWS Command Line Interface](https://aws.amazon.com/cli/) (AWS CLI). Read more about these files in the [Configuration and Credential Files](https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html) topic of the _AWS Command Line Interface User Guide_. While the shared credentials files are common between the AWS CLI and this Toolkit, there are some profiles that are supported by the AWS CLI that are not compatible with the Toolkit.

## User Flow

From the VS Code [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) (**View**, then **Command Palette**), type "AWS" and then select **AWS: Connect to AWS**. Existing credential profiles are shown, starting with the most recently used.
From the VS Code [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) (**View**, then **Command Palette**), type "AWS" and then select **AWS: Connect to AWS**. The credentials available for use by the Toolkit are shown, starting with those most recently used. If no credentials were found by the Toolkit, users are provided with an optional guide to setting up a basic Shared Credentials profile.

Select a credential profile from the list or define a new one (see "Adding Credentials" below). The Toolkit describes how to define credentials if none are found.
Instead of selecting Credentials from the list, users have the option to create a new Shared Credentials Profie (see "Adding Credentials" below).

The Toolkit connects to AWS, and the **AWS Explorer** shows the resources (such as AWS Lambda functions) that are accessible through the selected credentials. The VS Code status bar displays which credentials the Toolkit is using.
If credentials are selected from the list, they are validated by the Toolkit. If valid, the Toolkit uses them to connect to AWS.

Once the Toolkit connects to AWS, and the **AWS Explorer** shows resources (such as AWS Lambda functions) from the associated account. The credentials used by the Toolkit are shown in the VS Code status bar.

## Supported Credentials

The following types of credentials are supported:

* Credential profiles defined in the credentials files (see [Configuration and Credential Files](https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html)):
* Profiles with an access key and a secret key ([Named Profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html))
* Profiles that [assume a role](https://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html)
* Profiles that [assume a role](https://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html) and [use multifactor authentication](https://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html#cli-configure-role-mfa) (MFA)
* When connecting with credentials defined to assume a role and use MFA, the Toolkit prompts for an MFA token.
- Credential profiles defined in shared [credentials files](https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html):
- Profiles with an access key and a secret key ([Named Profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html))
- Profiles that [assume a role](https://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html)
- Profiles that [assume a role and use multifactor authentication](https://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html#cli-configure-role-mfa) (MFA)
- When connecting with credentials defined to assume a role and use MFA, the Toolkit prompts for an MFA token.
- profiles that use an [external credential process](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/external-credential-process.html)

## Adding Credentials

Additional credentials can be defined in the shared AWS credentials file. In the VS Code Command Palette, select **AWS: Create Credentials Profile**. The Toolkit does the following:

* If the shared AWS credentials file is not found, the Toolkit prompts for a profile name, an access key ID, and the corresponding secret access key. This information is then used to create an initial credentials file.
* If the shared AWS credentials file is found, it is opened in VS Code for editing, and the Toolkit provides information about how to change the file.
- If the shared AWS credentials file is not found, the Toolkit prompts for a profile name, an access key ID, and the corresponding secret access key. This information is then used to create an initial credentials file.
- If the shared AWS credentials file is found, it is opened in VS Code for editing, and the Toolkit provides information about how to change the file.

## Working with Regions in other Partitions

Partitions influence which regions are available for an account to operate in. Many popular regions belong to the "aws" partition, such as "us-east-1" (N. Virginia). Some regions such as "cn-north-1" (Beijing) reside in [other partitions](https://docs.aws.amazon.com/general/latest/gr/rande.html#learn-more).

Toolkit support for regions in other partitions is dependent on the type of credentials being used, as outlined below. Portions of the Toolkit's features may not be available in every partition.

### Partition support using Shared Credentials Profiles

If a profile contains a `region` key, its value will be used to determine the partition used by these credentials. When `region` is not found, the profile is assumed to have the region `us-east-1`.
5 changes: 4 additions & 1 deletion src/awsexplorer/awsExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as vscode from 'vscode'
import { AwsContext } from '../shared/awsContext'
import { getLogger, Logger } from '../shared/logger'
import { RegionProvider } from '../shared/regions/regionProvider'
import { getRegionsForActiveCredentials } from '../shared/regions/regionUtilities'
import { RefreshableAwsTreeProvider } from '../shared/treeview/awsTreeProvider'
import { AWSCommandTreeNode } from '../shared/treeview/nodes/awsCommandTreeNode'
import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase'
Expand Down Expand Up @@ -95,8 +96,10 @@ export class AwsExplorer implements vscode.TreeDataProvider<AWSTreeNodeBase>, Re
return [ROOT_NODE_SIGN_IN]
}

const partitionRegions = getRegionsForActiveCredentials(this.awsContext, this.regionProvider)

const userVisibleRegionCodes = await this.awsContext.getExplorerRegions()
const regionMap = toMap(await this.regionProvider.getRegionData(), r => r.regionCode)
const regionMap = toMap(partitionRegions, r => r.id)

return await makeChildrenNodes({
getChildNodes: async () => {
Expand Down
24 changes: 12 additions & 12 deletions src/awsexplorer/regionNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { TreeItemCollapsibleState } from 'vscode'
import { SchemasNode } from '../eventSchemas/explorer/schemasNode'
import { CloudFormationNode } from '../lambda/explorer/cloudFormationNodes'
import { LambdaNode } from '../lambda/explorer/lambdaNodes'
import { RegionInfo } from '../shared/regions/regionInfo'
import { Region } from '../shared/regions/endpoints'
import { RegionProvider } from '../shared/regions/regionProvider'
import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase'

Expand All @@ -17,22 +17,22 @@ import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase'
* an account's Lambda Functions and CloudFormation stacks for this region)
*/
export class RegionNode extends AWSTreeNodeBase {
private info: RegionInfo
private region: Region
private readonly childNodes: AWSTreeNodeBase[] = []

public get regionCode(): string {
return this.info.regionCode
return this.region.id
}

public get regionName(): string {
return this.info.regionName
return this.region.name
}

public constructor(info: RegionInfo, regionProvider: RegionProvider) {
super(info.regionName, TreeItemCollapsibleState.Expanded)
public constructor(region: Region, regionProvider: RegionProvider) {
super(region.name, TreeItemCollapsibleState.Expanded)
this.contextValue = 'awsRegionNode'
this.info = info
this.update(info)
this.region = region
this.update(region)

const serviceCandidates = [
{ serviceId: 'cloudformation', createFn: () => new CloudFormationNode(this.regionCode) },
Expand All @@ -49,10 +49,10 @@ export class RegionNode extends AWSTreeNodeBase {
return this.childNodes
}

public update(info: RegionInfo): void {
this.info = info
this.label = info.regionName
this.tooltip = `${info.regionName} [${info.regionCode}]`
public update(region: Region): void {
this.region = region
this.label = this.regionName
this.tooltip = `${this.regionName} [${this.regionCode}]`
}

private addChildNodeIfInRegion(
Expand Down
5 changes: 3 additions & 2 deletions src/credentials/loginManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { asString, CredentialsProviderId } from './providers/credentialsProvider
import { CredentialsProviderManager } from './providers/credentialsProviderManager'

export class LoginManager {
private readonly defaultCredentialsRegion = 'us-east-1'
private readonly credentialsStore: CredentialsStore = new CredentialsStore()

public constructor(private readonly awsContext: AwsContext) {}
Expand All @@ -43,8 +44,8 @@ export class LoginManager {
throw new Error(`No credentials found for id ${asString(credentialsProviderId)}`)
}

// TODO : Get a region relevant to the partition for these credentials -- https://github.com/aws/aws-toolkit-vscode/issues/188
const accountId = await getAccountId(storedCredentials.credentials, 'us-east-1')
const credentialsRegion = provider.getDefaultRegion() ?? this.defaultCredentialsRegion
const accountId = await getAccountId(storedCredentials.credentials, credentialsRegion)
if (!accountId) {
throw new Error('Could not determine Account Id for credentials')
}
Expand Down
35 changes: 29 additions & 6 deletions src/credentials/providers/sharedCredentialsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,28 @@ export class SharedCredentialsProvider implements CredentialsProvider {
}

public async getCredentials(): Promise<AWS.Credentials> {
const validationMessage = this.validate()
if (validationMessage) {
throw new Error(`Profile ${this.profileName} is not a valid Credential Profile: ${validationMessage}`)
}
// Profiles with references involving non-aws partitions need help getting the right STS endpoint
// when resolving SharedIniFileCredentials. We set the global sts configuration with a suitable region
// only to perform the resolve, then reset it.
// This hack can be removed when https://github.com/aws/aws-sdk-js/issues/3088 is addressed.
const originalStsConfiguration = AWS.config.sts

try {
const validationMessage = this.validate()
if (validationMessage) {
throw new Error(`Profile ${this.profileName} is not a valid Credential Profile: ${validationMessage}`)
}

const provider = new AWS.CredentialProviderChain([this.makeCredentialsProvider()])

const provider = new AWS.CredentialProviderChain([this.makeCredentialsProvider()])
// Profiles with references involving non-aws partitions need help getting the right STS endpoint
this.applyProfileRegionToGlobalStsConfig()

return provider.resolvePromise()
return await provider.resolvePromise()
} finally {
// Profiles with references involving non-aws partitions need help getting the right STS endpoint
AWS.config.sts = originalStsConfiguration
}
}

private hasProfileProperty(propertyName: string): boolean {
Expand Down Expand Up @@ -186,6 +200,15 @@ export class SharedCredentialsProvider implements CredentialsProvider {
await getMfaTokenFromUser(mfaSerial, this.profileName, callback)
})
}

private applyProfileRegionToGlobalStsConfig() {
if (!AWS.config.sts) {
AWS.config.sts = {}
}

AWS.config.sts.region = this.getDefaultRegion()
}

public static getCredentialsType(): string {
return SharedCredentialsProvider.CREDENTIALS_TYPE
}
Expand Down
7 changes: 3 additions & 4 deletions src/lambda/commands/createNewSamApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ext } from '../../shared/extensionGlobals'
import { fileExists } from '../../shared/filesystemUtilities'
import { getLogger } from '../../shared/logger'
import { RegionProvider } from '../../shared/regions/regionProvider'
import { getRegionsForActiveCredentials } from '../../shared/regions/regionUtilities'
import { getSamCliContext, SamCliContext } from '../../shared/sam/cli/samCliContext'
import { runSamCliInit, SamCliInitArgs } from '../../shared/sam/cli/samCliInit'
import { throwAndNotifyIfInvalid } from '../../shared/sam/cli/samCliValidationUtils'
Expand Down Expand Up @@ -96,10 +97,8 @@ export async function createNewSamApplication(
await validateSamCli(samCliContext.validator)

const currentCredentials = await awsContext.getCredentials()
const availableRegions = await regionProvider.getRegionData()
const schemasRegions = availableRegions.filter(region =>
regionProvider.isServiceInRegion('schemas', region.regionCode)
)
const availableRegions = getRegionsForActiveCredentials(awsContext, regionProvider)
const schemasRegions = availableRegions.filter(region => regionProvider.isServiceInRegion('schemas', region.id))

const wizardContext = new DefaultCreateNewSamAppWizardContext(currentCredentials, schemasRegions)
config = await new CreateNewSamAppWizard(wizardContext).run()
Expand Down
24 changes: 13 additions & 11 deletions src/lambda/wizards/samDeployWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const localize = nls.loadMessageBundle()

import * as path from 'path'
import * as vscode from 'vscode'
import { AwsContext } from '../../shared/awsContext'
import { samDeployDocUrl } from '../../shared/constants'
import { getLogger } from '../../shared/logger'
import { RegionProvider } from '../../shared/regions/regionProvider'
import { getRegionsForActiveCredentials } from '../../shared/regions/regionUtilities'
import { createHelpButton } from '../../shared/ui/buttons'
import * as input from '../../shared/ui/input'
import * as picker from '../../shared/ui/picker'
Expand Down Expand Up @@ -123,7 +125,7 @@ export class DefaultSamDeployWizardContext implements SamDeployWizardContext {
public readonly getOverriddenParameters = getOverriddenParameters
private readonly helpButton = createHelpButton(localize('AWS.command.help', 'View Documentation'))

public constructor(private readonly regionProvider: RegionProvider) {}
public constructor(private readonly regionProvider: RegionProvider, private readonly awsContext: AwsContext) {}

public get workspaceFolders(): vscode.Uri[] | undefined {
return (vscode.workspace.workspaceFolders || []).map(f => f.uri)
Expand Down Expand Up @@ -255,25 +257,25 @@ export class DefaultSamDeployWizardContext implements SamDeployWizardContext {
}
}

public async promptUserForRegion(initialRegionCode: string): Promise<string | undefined> {
const regionData = await this.regionProvider.getRegionData()
public async promptUserForRegion(initialRegionCode?: string): Promise<string | undefined> {
const partitionRegions = getRegionsForActiveCredentials(this.awsContext, this.regionProvider)

const quickPick = picker.createQuickPick<vscode.QuickPickItem>({
options: {
title: localize('AWS.samcli.deploy.region.prompt', 'Which AWS Region would you like to deploy to?'),
value: initialRegionCode || '',
value: initialRegionCode,
matchOnDetail: true,
ignoreFocusOut: true
},
items: regionData.map(r => ({
label: r.regionName,
detail: r.regionCode,
items: partitionRegions.map(region => ({
label: region.name,
detail: region.id,
// this is the only way to get this to show on going back
// this will make it so it always shows even when searching for something else
alwaysShow: r.regionCode === initialRegionCode,
alwaysShow: region.id === initialRegionCode,
description:
r.regionCode === initialRegionCode
? localize('AWS.samcli.wizard.selectedPreviously', 'Selected Previously')
region.id === initialRegionCode
? localize('AWS.wizard.selectedPreviously', 'Selected Previously')
: ''
})),
buttons: [this.helpButton, vscode.QuickInputButtons.Back]
Expand All @@ -291,7 +293,7 @@ export class DefaultSamDeployWizardContext implements SamDeployWizardContext {
})
const val = picker.verifySinglePickerOutput(choices)

return val ? val.detail : undefined
return val?.detail
}

/**
Expand Down
Loading

0 comments on commit c30b3c5

Please sign in to comment.