Skip to content

Commit 30c0fa7

Browse files
committed
Merge branch 'feature/v2-to-v3-migration' into migrate-schemas
2 parents ca08021 + 01da25f commit 30c0fa7

File tree

17 files changed

+1711
-303
lines changed

17 files changed

+1711
-303
lines changed

package-lock.json

Lines changed: 1179 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Amazon Q automatically refreshes expired IAM Credentials in Sagemaker instances"
4+
}

packages/amazonq/src/lsp/auth.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import * as crypto from 'crypto'
1717
import { LanguageClient } from 'vscode-languageclient'
1818
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
1919
import { Writable } from 'stream'
20-
import { onceChanged } from 'aws-core-vscode/utils'
20+
import { onceChanged, onceChangedWithComparator } from 'aws-core-vscode/utils'
2121
import { getLogger, oneMinute, isSageMaker } from 'aws-core-vscode/shared'
22-
import { isSsoConnection, isIamConnection } from 'aws-core-vscode/auth'
22+
import { isSsoConnection, isIamConnection, areCredentialsEqual } from 'aws-core-vscode/auth'
2323

2424
export const encryptionKey = crypto.randomBytes(32)
2525

@@ -108,7 +108,10 @@ export class AmazonQLspAuth {
108108
this.client.info(`UpdateBearerToken: ${JSON.stringify(request)}`)
109109
}
110110

111-
public updateIamCredentials = onceChanged(this._updateIamCredentials.bind(this))
111+
public updateIamCredentials = onceChangedWithComparator(
112+
this._updateIamCredentials.bind(this),
113+
([prevCreds], [currentCreds]) => areCredentialsEqual(prevCreds, currentCreds)
114+
)
112115
private async _updateIamCredentials(credentials: any) {
113116
getLogger().info(
114117
`[SageMaker Debug] Updating IAM credentials - credentials received: ${credentials ? 'YES' : 'NO'}`

packages/core/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@
539539
"@types/whatwg-url": "^11.0.4",
540540
"@types/xml2js": "^0.4.11",
541541
"@vue/compiler-sfc": "^3.3.2",
542+
"aws-sdk-client-mock": "^4.1.0",
542543
"c8": "^9.0.0",
543544
"circular-dependency-plugin": "^5.2.2",
544545
"css-loader": "^6.10.0",
@@ -580,6 +581,8 @@
580581
"@aws-sdk/client-ec2": "<3.731.0",
581582
"@aws-sdk/client-glue": "^3.852.0",
582583
"@aws-sdk/client-iam": "<3.731.0",
584+
"@aws-sdk/client-iot": "~3.693.0",
585+
"@aws-sdk/client-iotsecuretunneling": "~3.693.0",
583586
"@aws-sdk/client-lambda": "<3.731.0",
584587
"@aws-sdk/client-s3": "<3.731.0",
585588
"@aws-sdk/client-s3-control": "^3.830.0",

packages/core/src/auth/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,7 @@ export class Auth implements AuthService, ConnectionManager {
862862

863863
private async createCachedCredentials(provider: CredentialsProvider) {
864864
const providerId = provider.getCredentialsId()
865+
getLogger().debug(`credentials: create cache credentials for ${provider.getProviderType()}`)
865866
globals.loginManager.store.invalidateCredentials(providerId)
866867
const { credentials, endpointUrl } = await globals.loginManager.store.upsertCredentials(providerId, provider)
867868
await globals.loginManager.validateCredentials(credentials, endpointUrl, provider.getDefaultRegion())

packages/core/src/auth/connection.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ export const isBuilderIdConnection = (conn?: Connection): conn is SsoConnection
7171
export const isValidCodeCatalystConnection = (conn?: Connection): conn is SsoConnection =>
7272
isSsoConnection(conn) && hasScopes(conn, scopesCodeCatalyst)
7373

74+
export const areCredentialsEqual = (creds1: any, creds2: any): boolean => {
75+
if (!creds1 || !creds2) {
76+
return creds1 === creds2
77+
}
78+
79+
return (
80+
creds1.accessKeyId === creds2.accessKeyId &&
81+
creds1.secretAccessKey === creds2.secretAccessKey &&
82+
creds1.sessionToken === creds2.sessionToken
83+
)
84+
}
85+
7486
export function hasScopes(target: SsoConnection | SsoProfile | string[], scopes: string[]): boolean {
7587
return scopes?.every((s) => (Array.isArray(target) ? target : target.scopes)?.includes(s))
7688
}

packages/core/src/auth/credentials/store.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@ export class CredentialsStore {
3131
* If the expiration property does not exist, it is assumed to never expire.
3232
*/
3333
public isValid(key: string): boolean {
34+
// Apply 60-second buffer similar to SSO token expiry logic
35+
const expirationBufferMs = 60000
36+
3437
if (this.credentialsCache[key]) {
3538
const expiration = this.credentialsCache[key].credentials.expiration
36-
return expiration !== undefined ? expiration >= new globals.clock.Date() : true
39+
const now = new globals.clock.Date()
40+
const bufferedNow = new globals.clock.Date(now.getTime() + expirationBufferMs)
41+
return expiration !== undefined ? expiration >= bufferedNow : true
3742
}
38-
43+
getLogger().debug(`credentials: no credentials found for ${key}`)
3944
return false
4045
}
4146

packages/core/src/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export {
1919
getTelemetryMetadataForConn,
2020
isIamConnection,
2121
isSsoConnection,
22+
areCredentialsEqual,
2223
} from './connection'
2324
export { Auth } from './auth'
2425
export { CredentialsStore } from './credentials/store'

packages/core/src/awsService/iot/commands/attachCertificate.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { createQuickPick, DataQuickPickItem } from '../../../shared/ui/pickerPro
1212
import { PromptResult } from '../../../shared/ui/prompter'
1313
import { IotClient } from '../../../shared/clients/iotClient'
1414
import { isValidResponse } from '../../../shared/wizards/wizard'
15-
import { Iot } from 'aws-sdk'
15+
import { Certificate, ListCertificatesResponse } from '@aws-sdk/client-iot'
1616

1717
export type CertGen = typeof getCertList
1818

@@ -53,8 +53,8 @@ export async function attachCertificateCommand(node: IotThingNode, promptFun = p
5353
/**
5454
* Prompts the user to pick a certificate to attach.
5555
*/
56-
async function promptForCert(iot: IotClient, certFetch: CertGen): Promise<PromptResult<Iot.Certificate>> {
57-
const placeHolder: DataQuickPickItem<Iot.Certificate> = {
56+
async function promptForCert(iot: IotClient, certFetch: CertGen): Promise<PromptResult<Certificate>> {
57+
const placeHolder: DataQuickPickItem<Certificate> = {
5858
label: 'No certificates found',
5959
data: undefined,
6060
}
@@ -71,10 +71,10 @@ async function promptForCert(iot: IotClient, certFetch: CertGen): Promise<Prompt
7171
*/
7272
async function* getCertList(iot: IotClient) {
7373
let marker: string | undefined = undefined
74-
let filteredCerts: Iot.Certificate[]
74+
let filteredCerts: Certificate[]
7575
do {
7676
try {
77-
const certResponse: Iot.ListCertificatesResponse = await iot.listCertificates({ marker })
77+
const certResponse: ListCertificatesResponse = await iot.listCertificates({ marker })
7878
marker = certResponse.nextMarker
7979

8080
/* These fields should always be defined when using the above API,

packages/core/src/lambda/remoteDebugging/ldkClient.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import { IoTSecureTunneling, Lambda } from 'aws-sdk'
7+
import { Lambda } from 'aws-sdk'
8+
import {
9+
CloseTunnelCommand,
10+
IoTSecureTunnelingClient,
11+
ListTunnelsCommand,
12+
OpenTunnelCommand,
13+
RotateTunnelAccessTokenCommand,
14+
} from '@aws-sdk/client-iotsecuretunneling'
815
import { getClientId } from '../../shared/telemetry/util'
916
import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
1017
import { LocalProxy } from './localProxy'
@@ -61,7 +68,7 @@ export class LdkClient {
6168
private localProxy: LocalProxy | undefined
6269
private static instanceCreating = false
6370
private lambdaClientCache: Map<string, DefaultLambdaClient> = new Map()
64-
private iotSTClientCache: Map<string, IoTSecureTunneling> = new Map()
71+
private iotSTClientCache: Map<string, IoTSecureTunnelingClient> = new Map()
6572

6673
constructor() {}
6774

@@ -97,9 +104,9 @@ export class LdkClient {
97104
return this.lambdaClientCache.get(region)!
98105
}
99106

100-
private async getIoTSTClient(region: string): Promise<IoTSecureTunneling> {
107+
private getIoTSTClient(region: string): IoTSecureTunnelingClient {
101108
if (!this.iotSTClientCache.has(region)) {
102-
this.iotSTClientCache.set(region, await getIoTSTClientWithAgent(region))
109+
this.iotSTClientCache.set(region, getIoTSTClientWithAgent(region))
103110
}
104111
return this.iotSTClientCache.get(region)!
105112
}
@@ -124,13 +131,13 @@ export class LdkClient {
124131
const vscodeUuid = getClientId(globals.globalState)
125132

126133
// Create IoTSecureTunneling client
127-
const iotSecureTunneling = await this.getIoTSTClient(region)
134+
const iotSecureTunneling = this.getIoTSTClient(region)
128135

129136
// Define tunnel identifier
130137
const tunnelIdentifier = `RemoteDebugging+${vscodeUuid}`
131138
const timeoutInMinutes = 720
132139
// List existing tunnels
133-
const listTunnelsResponse = await iotSecureTunneling.listTunnels({}).promise()
140+
const listTunnelsResponse = await iotSecureTunneling.send(new ListTunnelsCommand({}))
134141

135142
// Find tunnel with our identifier
136143
const existingTunnel = listTunnelsResponse.tunnelSummaries?.find(
@@ -150,20 +157,20 @@ export class LdkClient {
150157
return rotateResponse
151158
} else {
152159
// Close tunnel if less than 15 minutes remaining
153-
await iotSecureTunneling
154-
.closeTunnel({
160+
await iotSecureTunneling.send(
161+
new CloseTunnelCommand({
155162
tunnelId: existingTunnel.tunnelId,
156163
delete: false,
157164
})
158-
.promise()
165+
)
159166

160167
getLogger().info(`Closed tunnel ${existingTunnel.tunnelId} with less than 15 minutes remaining`)
161168
}
162169
}
163170

164171
// Create new tunnel
165-
const openTunnelResponse = await iotSecureTunneling
166-
.openTunnel({
172+
const openTunnelResponse = await iotSecureTunneling.send(
173+
new OpenTunnelCommand({
167174
description: tunnelIdentifier,
168175
timeoutConfig: {
169176
maxLifetimeTimeoutMinutes: timeoutInMinutes, // 12 hours
@@ -172,7 +179,7 @@ export class LdkClient {
172179
services: ['WSS'],
173180
},
174181
})
175-
.promise()
182+
)
176183

177184
getLogger().info(`Created new tunnel with ID: ${openTunnelResponse.tunnelId}`)
178185

@@ -189,13 +196,13 @@ export class LdkClient {
189196
// Refresh tunnel tokens
190197
async refreshTunnelTokens(tunnelId: string, region: string): Promise<TunnelInfo | undefined> {
191198
try {
192-
const iotSecureTunneling = await this.getIoTSTClient(region)
193-
const rotateResponse = await iotSecureTunneling
194-
.rotateTunnelAccessToken({
199+
const iotSecureTunneling = this.getIoTSTClient(region)
200+
const rotateResponse = await iotSecureTunneling.send(
201+
new RotateTunnelAccessTokenCommand({
195202
tunnelId: tunnelId,
196203
clientMode: 'ALL',
197204
})
198-
.promise()
205+
)
199206

200207
return {
201208
tunnelID: tunnelId,

0 commit comments

Comments
 (0)