Skip to content

Commit 0a26c22

Browse files
committed
merge in necessary pre-req
2 parents 53a678b + b0c1f02 commit 0a26c22

File tree

4 files changed

+127
-197
lines changed

4 files changed

+127
-197
lines changed

src/ec2/model.ts

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { IAM, SSM } from 'aws-sdk'
99
import { Ec2Selection } from './utils'
1010
import { getOrInstallCli } from '../shared/utilities/cliUtils'
1111
import { isCloud9 } from '../shared/extensionUtilities'
12-
import { ToolkitError, isAwsError } from '../shared/errors'
12+
import { ToolkitError } from '../shared/errors'
1313
import { SsmClient } from '../shared/clients/ssmClient'
1414
import { Ec2Client } from '../shared/clients/ec2Client'
1515
import { VscodeRemoteConnection, ensureDependencies, openRemoteTerminal } from '../shared/remoteSession'
@@ -42,6 +42,10 @@ export class Ec2ConnectionManager {
4242
'https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html'
4343
)
4444

45+
private ssmAgentDocumentationUri = vscode.Uri.parse(
46+
'https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html'
47+
)
48+
4549
public constructor(readonly regionCode: string) {
4650
this.ssmClient = this.createSsmSdkClient()
4751
this.ec2Client = this.createEc2SdkClient()
@@ -60,31 +64,18 @@ export class Ec2ConnectionManager {
6064
return new DefaultIamClient(this.regionCode)
6165
}
6266

63-
protected async getAttachedPolicies(instanceId: string): Promise<IAM.AttachedPolicy[]> {
64-
const IamRole = await this.ec2Client.getAttachedIamRole(instanceId)
65-
if (!IamRole?.Arn) {
66-
return []
67-
}
68-
try {
69-
const attachedPolicies = await this.iamClient.listAttachedRolePolicies(IamRole.Arn)
70-
return attachedPolicies
71-
} catch (e) {
72-
if (isAwsError(e) && e.code == 'NoSuchEntity') {
73-
const errorMessage = `Attached role does not exist in IAM: ${IamRole.Arn}.`
74-
getLogger().error(`ec2: ${errorMessage}`)
75-
throw ToolkitError.chain(e, errorMessage, {
76-
code: e.code,
77-
documentationUri: this.policyDocumentationUri,
78-
})
79-
}
80-
throw ToolkitError.chain(e as Error, `Failed to check policies for EC2 instance: ${instanceId}`, {
81-
code: 'PolicyCheck',
82-
})
67+
public async getAttachedIamRole(instanceId: string): Promise<IAM.Role | undefined> {
68+
const IamInstanceProfile = await this.ec2Client.getAttachedIamInstanceProfile(instanceId)
69+
if (IamInstanceProfile && IamInstanceProfile.Arn) {
70+
const IamRole = await this.iamClient.getIAMRoleFromInstanceProfile(IamInstanceProfile.Arn)
71+
return IamRole
8372
}
8473
}
8574

86-
public async hasProperPolicies(instanceId: string): Promise<boolean> {
87-
const attachedPolicies = (await this.getAttachedPolicies(instanceId)).map(policy => policy.PolicyName!)
75+
public async hasProperPolicies(IamRoleArn: string): Promise<boolean> {
76+
const attachedPolicies = (await this.iamClient.listAttachedRolePolicies(IamRoleArn)).map(
77+
policy => policy.PolicyName!
78+
)
8879
const requiredPolicies = ['AmazonSSMManagedInstanceCore', 'AmazonSSMManagedEC2InstanceDefaultPolicy']
8980

9081
return requiredPolicies.length !== 0 && requiredPolicies.every(policy => attachedPolicies.includes(policy))
@@ -100,42 +91,43 @@ export class Ec2ConnectionManager {
10091
throw new ToolkitError(generalErrorMessage + message, errorInfo)
10192
}
10293

103-
protected async throwPolicyError(selection: Ec2Selection) {
104-
const role = await this.ec2Client.getAttachedIamRole(selection.instanceId)
105-
106-
const baseMessage = 'Ensure an IAM role with the required policies is attached to the instance.'
107-
const messageExtension =
108-
role && role.Arn
109-
? `Found attached role ${role.Arn}.`
110-
: `Failed to find role attached to ${selection.instanceId}`
111-
const fullMessage = `${baseMessage} ${messageExtension}`
112-
113-
this.throwConnectionError(fullMessage, selection, {
114-
code: 'EC2SSMPermission',
115-
documentationUri: this.policyDocumentationUri,
116-
})
117-
}
118-
119-
public async checkForStartSessionError(selection: Ec2Selection): Promise<void> {
94+
private async checkForInstanceStatusError(selection: Ec2Selection): Promise<void> {
12095
const isInstanceRunning = await this.isInstanceRunning(selection.instanceId)
121-
const hasProperPolicies = await this.hasProperPolicies(selection.instanceId)
122-
const isSsmAgentRunning = (await this.ssmClient.getInstanceAgentPingStatus(selection.instanceId)) == 'Online'
12396

12497
if (!isInstanceRunning) {
12598
const message = 'Ensure the target instance is running and not currently starting, stopping, or stopped.'
12699
this.throwConnectionError(message, selection, { code: 'EC2SSMStatus' })
127100
}
101+
}
102+
103+
private async checkForInstancePermissionsError(selection: Ec2Selection): Promise<void> {
104+
const IamRole = await this.getAttachedIamRole(selection.instanceId)
105+
106+
if (!IamRole) {
107+
const message = `No IAM role attached to instance: ${selection.instanceId}`
108+
this.throwConnectionError(message, selection, { code: 'EC2SSMPermission' })
109+
}
110+
111+
const hasProperPolicies = await this.hasProperPolicies(IamRole!.Arn)
128112

129113
if (!hasProperPolicies) {
130-
await this.throwPolicyError(selection)
114+
const message = `Ensure an IAM role with the required policies is attached to the instance. Found attached role: ${
115+
IamRole!.Arn
116+
}`
117+
this.throwConnectionError(message, selection, {
118+
code: 'EC2SSMPermission',
119+
documentationUri: this.policyDocumentationUri,
120+
})
131121
}
122+
}
123+
124+
private async checkForInstanceSsmError(selection: Ec2Selection): Promise<void> {
125+
const isSsmAgentRunning = (await this.ssmClient.getInstanceAgentPingStatus(selection.instanceId)) == 'Online'
132126

133127
if (!isSsmAgentRunning) {
134128
this.throwConnectionError('Is SSM Agent running on the target instance?', selection, {
135129
code: 'EC2SSMAgentStatus',
136-
documentationUri: vscode.Uri.parse(
137-
'https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html'
138-
),
130+
documentationUri: this.ssmAgentDocumentationUri,
139131
})
140132
}
141133
}
@@ -147,6 +139,14 @@ export class Ec2ConnectionManager {
147139
})
148140
}
149141

142+
public async checkForStartSessionError(selection: Ec2Selection): Promise<void> {
143+
await this.checkForInstanceStatusError(selection)
144+
145+
await this.checkForInstancePermissionsError(selection)
146+
147+
await this.checkForInstanceSsmError(selection)
148+
}
149+
150150
private async openSessionInTerminal(session: Session, selection: Ec2Selection) {
151151
const ssmPlugin = await getOrInstallCli('session-manager-plugin', !isCloud9)
152152
const shellArgs = [JSON.stringify(session), selection.region, 'StartSession']

src/shared/clients/ec2Client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ export class Ec2Client {
100100
}
101101

102102
/**
103-
* Retrieve IAM role attached to given EC2 instance.
103+
* Gets the IAM Instance Profile (not role) attached to given EC2 instance.
104104
* @param instanceId target EC2 instance ID
105-
* @returns IAM role associated with instance or undefined if none exists.
105+
* @returns IAM Instance Profile associated with instance or undefined if none exists.
106106
*/
107-
public async getAttachedIamRole(instanceId: string): Promise<IamInstanceProfile | undefined> {
107+
public async getAttachedIamInstanceProfile(instanceId: string): Promise<IamInstanceProfile | undefined> {
108108
const association = await this.getIamInstanceProfileAssociation(instanceId)
109109
return association ? association.IamInstanceProfile : undefined
110110
}

src/shared/clients/iamClient.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import globals from '../extensionGlobals'
88
import { AsyncCollection } from '../utilities/asyncCollection'
99
import { pageableToCollection } from '../utilities/collectionUtils'
1010
import { ClassToInterfaceType } from '../utilities/tsUtils'
11+
import { ToolkitError } from '../errors'
1112

1213
export type IamClient = ClassToInterfaceType<DefaultIamClient>
1314

@@ -93,4 +94,14 @@ export class DefaultIamClient {
9394

9495
return policies
9596
}
97+
98+
public async getIAMRoleFromInstanceProfile(instanceProfileArn: string): Promise<IAM.Role> {
99+
const client = await this.createSdkClient()
100+
const instanceProfileName = this.getFriendlyName(instanceProfileArn)
101+
const response = await client.getInstanceProfile({ InstanceProfileName: instanceProfileName }).promise()
102+
if (response.InstanceProfile.Roles.length === 0) {
103+
throw new ToolkitError(`Failed to find IAM role associated with Instance profile ${instanceProfileArn}`)
104+
}
105+
return response.InstanceProfile.Roles[0]
106+
}
96107
}

0 commit comments

Comments
 (0)