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

feat(amazonq): Enable SageMaker SSO Users to use AmazonQ Chat & Code completion using their Credentials & Q Pro-tier profile #6338

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

ahusseinali
Copy link
Contributor

Problem

SageMaker Amazon Q Pro-tier users didn't have the ability to use their pro-tier profile ARN when using Amazon Q Chat & Code Completion. Previously, we had SageMaker pro-tier SSO users unable to automatically use their Code Editor login credentials to create an auth connection with Amazon Q APIs. This meant they have to re-login in order to use AmazonQ chat & Code Completion.

One big obstacle to enable auto-login for SSO users was that their credentials are stored in HttpOnly Cookie and a change had to be set in place in SageMaker Code Editor environment in order for AmazonQ extension to be able to read the cookie value and send it as bearer token.

Solution

Along with a change implemented for SageMaker Code Editor in https://github.com/aws/sagemaker-code-editor/pull/99/files
This PR implements the following:

  1. SageMaker client to read SageMaker user domain status to identify whether pro-tier is enabled or not & read the cookie value through custom SageMaker code editor command.
    1.1. In case Pro-tier is not enabled, the login flow is similar to IAM SageMaker users (using Container environment variables)
    1.2. The custom command execution stores cookie values into a file and the client reads the value from that file.
  2. SageMakerSSOTokenProvider: Custom token provider parallel to the original SSO Token provider. It doesn't go through any creation UI logic as the token already exists in file. This provider polls for token refresh every 5 minutes integrating with the client mentioned in Support listing multiple user-selected regions in explorer #1
  3. Pass Q Pro-tier ProfileARN (if it is enabled) to API calls that need it for SSO users
  4. Change the connection validation logic to account for valid pre-created SSO connections for SageMaker environment.
  5. Add Auto connect logic for SSO user flow in SageMaker

Followup steps

Simplify the logic for communicating the SSO credentials and creating a connection based on such credentials, by letting SageMaker Code Editor create a standard AWS Credentials File & adding a watcher logic in AWS toolkit codebase to automatically sync with such file existence


  • Treat all work as PUBLIC. Private feature/x branches will not be squash-merged at release time.
  • Your code changes must meet the guidelines in CONTRIBUTING.md.
  • License: I confirm that my contribution is made under the terms of the Apache 2.0 license.

@ahusseinali ahusseinali requested review from a team as code owners January 10, 2025 21:00
Copy link

  • This pull request modifies code in src/* but no tests were added/updated.
    • Confirm whether tests should be added or ensure the PR description explains why tests are not required.

@ahusseinali ahusseinali changed the title Enable SageMaker SSO Users to use AmazonQ Chat & Code completion using their Credentials & Q Pro-tier profile feat(amazonq): Enable SageMaker SSO Users to use AmazonQ Chat & Code completion using their Credentials & Q Pro-tier profile Jan 10, 2025
Comment on lines +32 to +35
let scopes = [...scopesSsoAccountAccess]
if (isAmazonQ()) {
scopes = [...scopes, ...SageMakerSpaceClient.getQConnectionScopes()]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this token provider would work with toolkit. Is that intended?

Are the SSO scopes required? If not, do not attach them.

Suggested change
let scopes = [...scopesSsoAccountAccess]
if (isAmazonQ()) {
scopes = [...scopes, ...SageMakerSpaceClient.getQConnectionScopes()]
}
const scopes = [isAmazonQ() ? ...SageMakerSpaceClient.getQConnectionScopes() : ...scopesSsoAccountAccess]

}

public static getQConnectionScopes() {
return ['codewhisperer:completions', 'codewhisperer:conversations']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give users all amazon Q scopes? Why are we only giving this subset

@@ -0,0 +1,4 @@
{
"type": "Feature",
"description": "Enable SageMaker SSO user access token & Profile ARN to be used for accessing AmazonQ & CodeWhisperer features"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"CodeWhisperer" is no more

Suggested change
"description": "Enable SageMaker SSO user access token & Profile ARN to be used for accessing AmazonQ & CodeWhisperer features"
"description": "Auth: Enable SSO access for SageMaker users."

@@ -963,6 +1003,20 @@ export class Auth implements AuthService, ConnectionManager {
return this.authenticate(id, refresh)
}

public async tryAutoConnectSageMaker(): Promise<StatefulConnection | undefined> {
try {
const sagemakerConnection = await this.createSageMakerSsoConnection()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the connection already exist in a saved state from a prior session?

Comment on lines +61 to +63
private constructor(sageMakerClient?: DefaultSageMakerClient) {
this.sageMakerClient = sageMakerClient ?? new DefaultSageMakerClient(this.getDefaultRegion())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this pattern makes sense. This class is a singleton accessible via getInstance only, so you can never pass a custom sageMakerClient. The constructor is private.

Comment on lines +69 to +74
public static getInstance(): SageMakerSpaceClient {
if (!this._instance) {
this._instance = new SageMakerSpaceClient()
}
return this._instance
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow the same format that we have in other singletons for consistency

public static get instance() {
if (this.#instance !== undefined) {
return this.#instance
}
const self = (this.#instance = new this())
return self
}

Suggested change
public static getInstance(): SageMakerSpaceClient {
if (!this._instance) {
this._instance = new SageMakerSpaceClient()
}
return this._instance
}
public static get instance() {
if (!this.#instance) {
this.#instance = new SageMakerSpaceClient()
}
return this.#instance
}

return this.cachedCookies
}

public async getAmazonQProfileArn(): Promise<string | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a profile ARN and why do we want it? Please add a docstring

}

// Manually expire the token every 5 minutes as token refresh is handled externally by Sagemaker
const TOKEN_EXPIRY = 5 * 60 * 1000
Copy link
Contributor

@hayemaxi hayemaxi Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use constants in

export const oneMinute = oneSecond * 60

e.g.

Suggested change
const TOKEN_EXPIRY = 5 * 60 * 1000
const TOKEN_EXPIRY = oneMinute * 5

Comment on lines +16 to +22
authMode?: 'Sso' | 'Iam'
expiryTime?: number
ssoExpiryTimestamp?: number
studioUserProfileName?: string
redirectURL?: string
AccessToken?: string
StudioSessionToken?: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing is guaranteed to exist in the sagemaker cookie? Would be nice if we had some documentation about sagemaker expectations (if it doesn't already exist)


// Manually expire the token every 5 minutes as token refresh is handled externally by Sagemaker
const TOKEN_EXPIRY = 5 * 60 * 1000
const DEFAULT_REGION = 'us-east-1'
Copy link
Contributor

@hayemaxi hayemaxi Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have logic for this. Use

return this.awsContext.getCredentialDefaultRegion() ?? defaultRegion

in globals, e.g.:

globals.regionProvider

@@ -59,3 +59,4 @@ export { i18n } from './i18n-helper'
export * from './icons'
export * as textDocumentUtil from './utilities/textDocumentUtilities'
export { TabTypeDataMap } from '../amazonq/webview/ui/tabs/constants'
export * from './sagemaker/client/sagemaker'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is packages/amazonq going to use this soon? Otherwise this isn't needed.

Comment on lines +798 to +804
let useDefaultCredentials = true

if (isSageMaker() && isAmazonQ()) {
useDefaultCredentials = !(await SageMakerSpaceClient.getInstance().getAmazonQProfileArn())
}

if (useDefaultCredentials) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let useDefaultCredentials = true
if (isSageMaker() && isAmazonQ()) {
useDefaultCredentials = !(await SageMakerSpaceClient.getInstance().getAmazonQProfileArn())
}
if (useDefaultCredentials) {
const useDefaultCredentials = isSageMaker() && isAmazonQ() && !(await SageMakerSpaceClient.getInstance().getAmazonQProfileArn())
if (useDefaultCredentials) {


return {
scopes,
startUrl: 'sagemaker',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a URL... can we use sagemakerCookie.redirectURL? I'm worried about callers using this field with functions expecting a real URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants