Skip to content

Commit 6276983

Browse files
authored
feat(runtimes): add handlers for IAM and STS credentials management (#599)
This change adds request handlers and their associated types to manage IAM and STS credentials from aws-lsp-identity.
1 parent 8ab0530 commit 6276983

File tree

6 files changed

+213
-19
lines changed

6 files changed

+213
-19
lines changed

runtimes/protocol/identity-management.ts

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
IamCredentials,
23
LSPErrorCodes,
34
ProgressType,
45
ProtocolNotificationType,
@@ -15,21 +16,28 @@ export const AwsErrorCodes = {
1516
E_CANNOT_OVERWRITE_SSO_SESSION: 'E_CANNOT_OVERWRITE_SSO_SESSION',
1617
E_CANNOT_READ_SHARED_CONFIG: 'E_CANNOT_READ_SHARED_CONFIG',
1718
E_CANNOT_READ_SSO_CACHE: 'E_CANNOT_READ_SSO_CACHE',
19+
E_CANNOT_READ_STS_CACHE: 'E_CANNOT_READ_STS_CACHE',
1820
E_CANNOT_REFRESH_SSO_TOKEN: 'E_CANNOT_REFRESH_SSO_TOKEN',
21+
E_CANNOT_REFRESH_STS_CREDENTIAL: 'E_CANNOT_REFRESH_STS_CREDENTIAL',
1922
E_CANNOT_REGISTER_CLIENT: 'E_CANNOT_REGISTER_CLIENT',
2023
E_CANNOT_CREATE_SSO_TOKEN: 'E_CANNOT_CREATE_SSO_TOKEN',
24+
E_CANNOT_CREATE_STS_CREDENTIAL: 'E_CANNOT_CREATE_STS_CREDENTIAL',
2125
E_CANNOT_WRITE_SHARED_CONFIG: 'E_CANNOT_WRITE_SHARED_CONFIG',
2226
E_CANNOT_WRITE_SSO_CACHE: 'E_CANNOT_WRITE_SSO_CACHE',
27+
E_CANNOT_WRITE_STS_CACHE: 'E_CANNOT_WRITE_STS_CACHE',
2328
E_ENCRYPTION_REQUIRED: 'E_ENCRYPTION_REQUIRED',
2429
E_INVALID_PROFILE: 'E_INVALID_PROFILE',
2530
E_INVALID_SSO_CLIENT: 'E_INVALID_SSO_CLIENT',
2631
E_INVALID_SSO_SESSION: 'E_INVALID_SSO_SESSION',
2732
E_INVALID_SSO_TOKEN: 'E_INVALID_SSO_TOKEN',
33+
E_INVALID_STS_CREDENTIAL: 'E_INVALID_STS_CREDENTIAL',
2834
E_PROFILE_NOT_FOUND: 'E_PROFILE_NOT_FOUND',
2935
E_RUNTIME_NOT_SUPPORTED: 'E_RUNTIME_NOT_SUPPORTED',
3036
E_SSO_SESSION_NOT_FOUND: 'E_SSO_SESSION_NOT_FOUND',
3137
E_SSO_TOKEN_EXPIRED: 'E_SSO_TOKEN_EXPIRED',
38+
E_STS_CREDENTIAL_EXPIRED: 'E_STS_CREDENTIAL_EXPIRED',
3239
E_SSO_TOKEN_SOURCE_NOT_SUPPORTED: 'E_SSO_TOKEN_SOURCE_NOT_SUPPORTED',
40+
E_MFA_REQUIRED: 'E_MFA_REQUIRED',
3341
E_TIMEOUT: 'E_TIMEOUT',
3442
E_UNKNOWN: 'E_UNKNOWN',
3543
E_CANCELLED: 'E_CANCELLED',
@@ -47,10 +55,20 @@ export class AwsResponseError extends ResponseError<AwsResponseErrorData> {
4755
}
4856

4957
// listProfiles
50-
export type ProfileKind = 'Unknown' | 'SsoTokenProfile'
58+
export type ProfileKind =
59+
| 'Unknown'
60+
| 'SsoTokenProfile'
61+
| 'IamCredentialsProfile'
62+
| 'IamSourceProfileProfile'
63+
| 'IamCredentialSourceProfile'
64+
| 'IamCredentialProcessProfile'
5165

5266
export const ProfileKind = {
5367
SsoTokenProfile: 'SsoTokenProfile',
68+
IamCredentialsProfile: 'IamCredentialsProfile',
69+
IamSourceProfileProfile: 'IamSourceProfileProfile',
70+
IamCredentialSourceProfile: 'IamCredentialSourceProfile',
71+
IamCredentialProcessProfile: 'IamCredentialProcessProfile',
5472
Unknown: 'Unknown',
5573
} as const
5674

@@ -64,6 +82,18 @@ export interface Profile {
6482
settings?: {
6583
region?: string
6684
sso_session?: string
85+
aws_access_key_id?: string
86+
aws_secret_access_key?: string
87+
aws_session_token?: string
88+
role_arn?: string
89+
role_session_name?: string
90+
credential_process?: string
91+
credential_source?: string
92+
source_profile?: string
93+
mfa_serial?: string
94+
external_id?: string
95+
credential_cache?: string
96+
credential_cache_location?: string
6797
}
6898
}
6999

@@ -218,6 +248,56 @@ export const getSsoTokenRequestType = new ProtocolRequestType<
218248
void
219249
>('aws/identity/getSsoToken')
220250

251+
// getIamCredential
252+
export type IamCredentialId = string // Opaque identifier
253+
254+
export interface GetIamCredentialOptions {
255+
callStsOnInvalidIamCredential?: boolean
256+
validatePermissions?: boolean
257+
}
258+
259+
export const getIamCredentialOptionsDefaults = {
260+
callStsOnInvalidIamCredential: true,
261+
validatePermissions: true,
262+
} satisfies GetIamCredentialOptions
263+
264+
export interface GetIamCredentialParams {
265+
profileName: string
266+
options?: GetIamCredentialOptions
267+
}
268+
269+
export interface GetIamCredentialResult {
270+
id: IamCredentialId
271+
credentials: IamCredentials
272+
updateCredentialsParams: UpdateCredentialsParams
273+
}
274+
275+
export const getIamCredentialRequestType = new ProtocolRequestType<
276+
GetIamCredentialParams,
277+
GetIamCredentialResult,
278+
never,
279+
AwsResponseError,
280+
void
281+
>('aws/identity/getIamCredential')
282+
283+
// getMfaCode
284+
export interface GetMfaCodeParams {
285+
mfaSerial: string
286+
profileName: string
287+
}
288+
289+
export interface GetMfaCodeResult {
290+
code: string
291+
}
292+
293+
export const getMfaCodeRequestType = new ProtocolRequestType<
294+
GetMfaCodeParams,
295+
GetMfaCodeResult,
296+
never,
297+
AwsResponseError,
298+
void
299+
>('aws/identity/getMfaCode')
300+
221301
// invalidateSsoToken
222302
export interface InvalidateSsoTokenParams {
223303
ssoTokenId: SsoTokenId
@@ -236,6 +316,23 @@ export const invalidateSsoTokenRequestType = new ProtocolRequestType<
236316
void
237317
>('aws/identity/invalidateSsoToken')
238318

319+
// invalidateStsCredential
320+
export interface InvalidateStsCredentialParams {
321+
profileName: string
322+
}
323+
324+
export interface InvalidateStsCredentialResult {
325+
// Intentionally left blank
326+
}
327+
328+
export const invalidateStsCredentialRequestType = new ProtocolRequestType<
329+
InvalidateStsCredentialParams,
330+
InvalidateStsCredentialResult,
331+
never,
332+
AwsResponseError,
333+
void
334+
>('aws/identity/invalidateStsCredential')
335+
239336
// ssoTokenChanged
240337
export type Expired = 'Expired'
241338
export type Refreshed = 'Refreshed'
@@ -255,3 +352,20 @@ export interface SsoTokenChangedParams {
255352
export const ssoTokenChangedRequestType = new ProtocolNotificationType<SsoTokenChangedParams, void>(
256353
'aws/identity/ssoTokenChanged'
257354
)
355+
356+
// stsCredentialChanged
357+
export type StsCredentialChangedKind = Refreshed | Expired
358+
359+
export const StsCredentialChangedKind = {
360+
Expired: 'Expired',
361+
Refreshed: 'Refreshed',
362+
} as const
363+
364+
export interface StsCredentialChangedParams {
365+
kind: StsCredentialChangedKind
366+
stsCredentialId: IamCredentialId
367+
}
368+
369+
export const stsCredentialChangedRequestType = new ProtocolNotificationType<StsCredentialChangedParams, void>(
370+
'aws/identity/stsCredentialChanged'
371+
)

runtimes/runtimes/auth/standalone/encryption.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Readable } from 'stream'
22
import { CompactEncrypt } from 'jose'
3+
import { GetIamCredentialResult, GetSsoTokenResult } from '../../../protocol'
34

45
export function shouldWaitForEncryptionKey(): boolean {
56
return process.argv.some(arg => arg === '--set-credentials-encryption-key')
@@ -98,6 +99,48 @@ export function encryptObjectWithKey(request: Object, key: string, alg?: string,
9899
.encrypt(keyBuffer)
99100
}
100101

102+
/**
103+
* Encrypts the SSO access tokens inside the result object with the provided key
104+
*/
105+
export async function encryptSsoResultWithKey(request: GetSsoTokenResult, key: string): Promise<GetSsoTokenResult> {
106+
if (request.ssoToken.accessToken) {
107+
request.ssoToken.accessToken = await encryptObjectWithKey(request.ssoToken.accessToken, key)
108+
}
109+
if (request.updateCredentialsParams.data && !request.updateCredentialsParams.encrypted) {
110+
request.updateCredentialsParams.data = await encryptObjectWithKey(
111+
// decodeCredentialsRequestToken expects nested 'data' fields
112+
{ data: request.updateCredentialsParams.data },
113+
key
114+
)
115+
request.updateCredentialsParams.encrypted = true
116+
}
117+
return request
118+
}
119+
120+
/**
121+
* Encrypts the IAM credentials inside the result object with the provided key
122+
*/
123+
export async function encryptIamResultWithKey(
124+
request: GetIamCredentialResult,
125+
key: string
126+
): Promise<GetIamCredentialResult> {
127+
request.credentials = {
128+
accessKeyId: await encryptObjectWithKey(request.credentials.accessKeyId, key),
129+
secretAccessKey: await encryptObjectWithKey(request.credentials.secretAccessKey, key),
130+
...(request.credentials.sessionToken
131+
? { sessionToken: await encryptObjectWithKey(request.credentials.sessionToken, key) }
132+
: {}),
133+
}
134+
if (!request.updateCredentialsParams.encrypted) {
135+
request.updateCredentialsParams.data = await encryptObjectWithKey(
136+
{ data: request.updateCredentialsParams.data },
137+
key
138+
)
139+
request.updateCredentialsParams.encrypted = true
140+
}
141+
return request
142+
}
143+
101144
/**
102145
* Check if a message is an encrypted JWE message with the provided key management algorithm and encoding
103146
* As per RFC-7516:

runtimes/runtimes/base-runtime.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,15 @@ import { observe } from './lsp'
8585
import { LspRouter } from './lsp/router/lspRouter'
8686
import { LspServer } from './lsp/router/lspServer'
8787
import {
88+
getIamCredentialRequestType,
8889
getSsoTokenRequestType,
90+
invalidateStsCredentialRequestType,
8991
invalidateSsoTokenRequestType,
9092
listProfilesRequestType,
9193
ssoTokenChangedRequestType,
9294
updateProfileRequestType,
95+
stsCredentialChangedRequestType,
96+
getMfaCodeRequestType,
9397
} from '../protocol/identity-management'
9498
import { IdentityManagement } from '../server-interface/identity-management'
9599
import { WebBase64Encoding } from './encoding'
@@ -208,8 +212,12 @@ export const baseRuntime = (connections: { reader: MessageReader; writer: Messag
208212
onListProfiles: handler => lspConnection.onRequest(listProfilesRequestType, handler),
209213
onUpdateProfile: handler => lspConnection.onRequest(updateProfileRequestType, handler),
210214
onGetSsoToken: handler => lspConnection.onRequest(getSsoTokenRequestType, handler),
215+
onGetIamCredential: handler => lspConnection.onRequest(getIamCredentialRequestType, handler),
211216
onInvalidateSsoToken: handler => lspConnection.onRequest(invalidateSsoTokenRequestType, handler),
217+
onInvalidateStsCredential: handler => lspConnection.onRequest(invalidateStsCredentialRequestType, handler),
212218
sendSsoTokenChanged: params => lspConnection.sendNotification(ssoTokenChangedRequestType, params),
219+
sendStsCredentialChanged: params => lspConnection.sendNotification(stsCredentialChangedRequestType, params),
220+
sendGetMfaCode: params => lspConnection.sendRequest(getMfaCodeRequestType, params),
213221
}
214222

215223
// Set up auth without encryption

runtimes/runtimes/standalone.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@ import {
2828
didWriteFileNotificationType,
2929
didAppendFileNotificationType,
3030
didCreateDirectoryNotificationType,
31+
getIamCredentialRequestType,
32+
GetIamCredentialParams,
3133
ShowOpenDialogParams,
3234
ShowOpenDialogRequestType,
35+
stsCredentialChangedRequestType,
36+
getMfaCodeRequestType,
3337
} from '../protocol'
3438
import { ProposedFeatures, createConnection } from 'vscode-languageserver/node'
3539
import {
40+
encryptIamResultWithKey,
3641
EncryptionInitialization,
3742
encryptObjectWithKey,
43+
encryptSsoResultWithKey,
3844
readEncryptionDetails,
3945
shouldWaitForEncryptionKey,
4046
validateEncryptionDetails,
@@ -50,6 +56,7 @@ import {
5056
getSsoTokenRequestType,
5157
IdentityManagement,
5258
invalidateSsoTokenRequestType,
59+
invalidateStsCredentialRequestType,
5360
listProfilesRequestType,
5461
ssoTokenChangedRequestType,
5562
updateProfileRequestType,
@@ -302,31 +309,29 @@ export const standalone = (props: RuntimeProps) => {
302309
lspConnection.onRequest(
303310
getSsoTokenRequestType,
304311
async (params: GetSsoTokenParams, token: CancellationToken) => {
305-
const result = await handler(params, token)
306-
307-
// Encrypt SsoToken.accessToken before sending to client
312+
let result = await handler(params, token)
308313
if (result && !(result instanceof Error) && encryptionKey) {
309-
if (result.ssoToken.accessToken) {
310-
result.ssoToken.accessToken = await encryptObjectWithKey(
311-
result.ssoToken.accessToken,
312-
encryptionKey
313-
)
314-
}
315-
if (result.updateCredentialsParams.data && !result.updateCredentialsParams.encrypted) {
316-
result.updateCredentialsParams.data = await encryptObjectWithKey(
317-
// decodeCredentialsRequestToken expects nested 'data' fields
318-
{ data: result.updateCredentialsParams.data },
319-
encryptionKey
320-
)
321-
result.updateCredentialsParams.encrypted = true
322-
}
314+
result = await encryptSsoResultWithKey(result, encryptionKey)
315+
}
316+
return result
317+
}
318+
),
319+
onGetIamCredential: handler =>
320+
lspConnection.onRequest(
321+
getIamCredentialRequestType,
322+
async (params: GetIamCredentialParams, token: CancellationToken) => {
323+
let result = await handler(params, token)
324+
if (result && !(result instanceof Error) && encryptionKey) {
325+
result = await encryptIamResultWithKey(result, encryptionKey)
323326
}
324-
325327
return result
326328
}
327329
),
328330
onInvalidateSsoToken: handler => lspConnection.onRequest(invalidateSsoTokenRequestType, handler),
331+
onInvalidateStsCredential: handler => lspConnection.onRequest(invalidateStsCredentialRequestType, handler),
329332
sendSsoTokenChanged: params => lspConnection.sendNotification(ssoTokenChangedRequestType, params),
333+
sendStsCredentialChanged: params => lspConnection.sendNotification(stsCredentialChangedRequestType, params),
334+
sendGetMfaCode: params => lspConnection.sendRequest(getMfaCodeRequestType, params),
330335
}
331336

332337
const credentialsProvider: CredentialsProvider = auth.getCredentialsProvider()

runtimes/server-interface/identity-management.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import {
22
AwsResponseError,
3+
GetIamCredentialParams,
4+
GetIamCredentialResult,
35
GetSsoTokenParams,
46
GetSsoTokenResult,
57
InvalidateSsoTokenParams,
68
InvalidateSsoTokenResult,
9+
InvalidateStsCredentialParams,
10+
InvalidateStsCredentialResult,
711
ListProfilesParams,
812
ListProfilesResult,
13+
GetMfaCodeParams,
914
SsoTokenChangedParams,
15+
StsCredentialChangedParams,
1016
UpdateProfileParams,
1117
UpdateProfileResult,
18+
GetMfaCodeResult,
1219
} from '../protocol/identity-management'
1320
import { RequestHandler } from '../protocol'
1421

@@ -27,9 +34,25 @@ export type IdentityManagement = {
2734
handler: RequestHandler<GetSsoTokenParams, GetSsoTokenResult | undefined | null, AwsResponseError>
2835
) => void
2936

37+
onGetIamCredential: (
38+
handler: RequestHandler<GetIamCredentialParams, GetIamCredentialResult | undefined | null, AwsResponseError>
39+
) => void
40+
3041
onInvalidateSsoToken: (
3142
handler: RequestHandler<InvalidateSsoTokenParams, InvalidateSsoTokenResult | undefined | null, AwsResponseError>
3243
) => void
3344

45+
onInvalidateStsCredential: (
46+
handler: RequestHandler<
47+
InvalidateStsCredentialParams,
48+
InvalidateStsCredentialResult | undefined | null,
49+
AwsResponseError
50+
>
51+
) => void
52+
3453
sendSsoTokenChanged: (params: SsoTokenChangedParams) => void
54+
55+
sendStsCredentialChanged: (params: StsCredentialChangedParams) => void
56+
57+
sendGetMfaCode: (params: GetMfaCodeParams) => Promise<GetMfaCodeResult>
3558
}

types/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type IamCredentials = {
22
readonly accessKeyId: string
33
readonly secretAccessKey: string
44
readonly sessionToken?: string
5+
readonly expiration?: Date
56
}
67

78
export type BearerCredentials = {

0 commit comments

Comments
 (0)