Skip to content

Commit

Permalink
feat: initial support for cognito federated indentities
Browse files Browse the repository at this point in the history
  • Loading branch information
Rob-Powell authored and bigadsoleiman committed Jun 10, 2024
1 parent d3e4336 commit 564f1e0
Show file tree
Hide file tree
Showing 21 changed files with 695 additions and 16 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,7 @@ lib/user-interface/react-app/src/API.ts
lib/user-interface/react-app/src/graphql/mutations.ts
lib/user-interface/react-app/src/graphql/queries.ts
lib/user-interface/react-app/src/graphql/subscriptions.ts

# js function
!lib/authentication/lambda/updateUserPoolClient/index.js
!lib/authentication/lambda/updateOidcSecret/index.js
151 changes: 151 additions & 0 deletions cli/magic-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import * as fs from "fs";
import { AWSCronValidator } from "./aws-cron-validator"
import { tz } from 'moment-timezone';
import { getData } from 'country-list';
import { randomBytes } from 'crypto';
import { StringUtils } from 'turbocommons-ts';

function getTimeZonesWithCurrentTime(): { message: string; name: string }[] {
const timeZones = tz.names(); // Get a list of all timezones
Expand Down Expand Up @@ -138,6 +140,15 @@ const embeddingModels = [
options.privateWebsite = config.privateWebsite;
options.certificate = config.certificate;
options.domain = config.domain;
options.cognitoFederationEnabled = config.cognitoFederation?.enabled;
options.cognitoCustomProviderName = config.cognitoFederation?.customProviderName;
options.cognitoCustomProviderType = config.cognitoFederation?.customProviderType;
options.cognitoCustomProviderSAMLMetadata = config.cognitoFederation?.customSAML?.metadataDocumentUrl;
options.cognitoCustomProviderOIDCClient = config.cognitoFederation?.customOIDC?.OIDCClient;
options.cognitoCustomProviderOIDCSecret = config.cognitoFederation?.customOIDC?.OIDCSecret;
options.cognitoCustomProviderOIDCIssuerURL = config.cognitoFederation?.customOIDC?.OIDCIssuerURL;
options.cognitoAutoRedirect = config.cognitoFederation?.autoRedirect;
options.cognitoDomain = config.cognitoFederation?.cognitoDomain;
options.cfGeoRestrictEnable = config.cfGeoRestrictEnable;
options.cfGeoRestrictList = config.cfGeoRestrictList;
options.bedrockEnable = config.bedrock?.enabled;
Expand Down Expand Up @@ -301,6 +312,123 @@ async function processCreateOptions(options: any): Promise<void> {
return !(this as any).state.answers.privateWebsite && !(this as any).state.answers.customPublicDomain;
},
},
{
type: "confirm",
name: "cognitoFederationEnabled",
message:
"Do you want to enable Federated (SSO) login with Cognito?",
initial: options.cognitoFederationEnabled || false,
},
{
type: "input",
name: "cognitoCustomProviderName",
message:
"Please enter the name of the SAML/OIDC Federated identity provider that is or will be setup in Cognito",
skip(): boolean {
return !(this as any).state.answers.cognitoFederationEnabled;
},
initial: options.cognitoCustomProviderName || "",
},
{
type: "select",
name: "cognitoCustomProviderType",
choices: [
{ message: "Custom Cognito SAML", name: "SAML" },
{ message: "Custom Cognito OIDC", name: "OIDC" },
{ message: "Setup in Cognito Later", name: "later" },
],
message:
"Do you want to setup a SAML or OIDC provider? or choose to do this later after install",
skip(): boolean {
(this as any).state._choices = (this as any).state.choices;
return !(this as any).state.answers.cognitoFederationEnabled;
},
initial: options.cognitoCustomProviderType || "",
},
{
type: "input",
name: "cognitoCustomProviderSAMLMetadata",
message:
"Provide a URL to a SAML metadata document. This document is issued by your SAML provider.",
validate(v: string) {
return ((this as any).skipped || StringUtils.isUrl(v)) ?
true : 'That does not look like a valid URL'
},
skip(): boolean {
if (!(this as any).state.answers.cognitoFederationEnabled){
return true;
}
return !(this as any).state.answers.cognitoCustomProviderType.includes("SAML");
},
initial: options.cognitoCustomProviderSAMLMetadata || "",
},
{
type: "input",
name: "cognitoCustomProviderOIDCClient",
message:
"Enter the client ID provided by OpenID Connect identity provider.",
validate(v: string) {
if ((this as any).skipped) {
return true
}
// Regular expression to match HH:MM format
const regex = /^[a-zA-Z0-9-_]{1,255}$/;
return regex.test(v) || 'Must only contain Alpha Numeric characters, "-" or "_" and be a maximum of 255 in length.';
},
skip(): boolean {
if (!(this as any).state.answers.cognitoFederationEnabled){
return true;
}
return !(this as any).state.answers.cognitoCustomProviderType.includes("OIDC");
},
initial: options.cognitoCustomProviderOIDCClient || "",
},
{
type: "input",
name: "cognitoCustomProviderOIDCSecret",
validate(v: string) {
const valid = secretManagerArnRegExp.test(v);
return (this as any).skipped || valid
? true
: "You need to enter an Secret Manager Secret arn";
},
message:
"Enter the secret manager ARN containing the OIDC client secret to use (see docs for info)",
skip(): boolean {
if (!(this as any).state.answers.cognitoFederationEnabled){
return true;
}
return !(this as any).state.answers.cognitoCustomProviderType.includes("OIDC");
},
initial: options.cognitoCustomProviderOIDCSecret || "",
},
{
type: "input",
name: "cognitoCustomProviderOIDCIssuerURL",
message:
"Enter the issuer URL you received from the OIDC provider.",
validate(v: string) {
return ((this as any).skipped || StringUtils.isUrl(v)) ?
true : 'That does not look like a valid URL'
},
skip(): boolean {
if (!(this as any).state.answers.cognitoFederationEnabled){
return true;
}
return !(this as any).state.answers.cognitoCustomProviderType.includes("OIDC");
},
initial: options.cognitoCustomProviderOIDCIssuerURL || "",
},
{
type: "confirm",
name: "cognitoAutoRedirect",
message:
"Would you like to automatically redirect users to this identity provider?",
skip(): boolean {
return !(this as any).state.answers.cognitoFederationEnabled;
},
initial: options.cognitoAutoRedirect || false,
},
{
type: "confirm",
name: "cfGeoRestrictEnable",
Expand Down Expand Up @@ -747,6 +875,8 @@ async function processCreateOptions(options: any): Promise<void> {
AWSCronValidator.validate(answers.sagemakerCronStopSchedule)
}

const randomSuffix = randomBytes(8).toString('hex');

// Create the config object
const config = {
prefix: answers.prefix,
Expand All @@ -759,6 +889,27 @@ async function processCreateOptions(options: any): Promise<void> {
privateWebsite: answers.privateWebsite,
certificate: answers.certificate,
domain: answers.domain,
cognitoFederation: answers.cognitoFederationEnabled
? {
enabled: answers.cognitoFederationEnabled,
autoRedirect: answers.cognitoAutoRedirect,
customProviderName: answers.cognitoCustomProviderName,
customProviderType: answers.cognitoCustomProviderType,
customSAML: answers.cognitoCustomProviderType == "SAML"
? {
metadataDocumentUrl: answers.cognitoCustomProviderSAMLMetadata
}
: undefined,
customOIDC: answers.cognitoCustomProviderType == "OIDC"
? {
OIDCClient: answers.cognitoCustomProviderOIDCClient,
OIDCSecret: answers.cognitoCustomProviderOIDCSecret,
OIDCIssuerURL: answers.cognitoCustomProviderOIDCIssuerURL,
}
: undefined,
cognitoDomain: options.cognitoDomain ? options.cognitoDomain : `llm-cb-${randomSuffix}`,
}
: undefined,
cfGeoRestrictEnable: answers.cfGeoRestrictEnable,
cfGeoRestrictList: answers.cfGeoRestrictList,
bedrock: answers.bedrockEnable
Expand Down
5 changes: 5 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export default defineConfig({
items: [
{ text: 'Custom Public Domain', link: '/documentation/custom-public-domain' },
{ text: 'Private Chatbot', link: '/documentation/private-chatbot' },
{ text: 'Cognito Federation', items: [
{ text: 'Cognito Overview', link: '/documentation/cognito/overview' },
{ text: 'Keycloak SAML example', link: '/documentation/cognito/keycloak-saml' },
{ text: 'Keycloak OIDC example', link: '/documentation/cognito/keycloak-oidc' },
]},
{ text: 'Model Requirements', link: '/documentation/model-requirements' },
{ text: 'Self-hosted models', link: '/documentation/self-hosted-models' },
{ text: 'Inference Script', link: '/documentation/inference-script' },
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions docs/documentation/cognito/keycloak-oidc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Keycloak and Cognito OIDC integration

[Keycloak](https://github.com/keycloak/keycloak) describes itself as:

> Open Source Identity and Access Management For Modern Applications and Services
This is a short guide on how to configure OIDC federation between cognito and keycloak. This guide is not a recommendation for keycloak and assumes a certain level of knowledge and familiarity with it.

This guide is for example purposes only and additional configuration may be required to meet your security/integration requirements.

1. Under the desired realm within keycloak navigate to clients and create a new client using the `create client` button and select `OpenID Connect`.
2. You will need to enter a `Client ID` this is something to identify the integration and then click `Next`
3. Toggle on `Client Authentication` and click `Next`
4. Finally click `Save`.
5. You will need to deploy the chatbot after finishing its confiugration before creating the OIDC client within Keycloak.
6. See details on [Cognito Overview](./overview.md) for the configuration options for federation
7. Use the `Client ID` set previously and grab the `Client Secret` from the `Credentials` tab within the previously created keycloak client.
7. You will need to supply the OIDC issuer base url for keycloak to the installer however. In Keycloak this is found under Realm Settings for the KeycLoak Realm you will create the client under. For example: `https://<keycloak-domain>/realms/NGINX/`
8. Once the chatbot has been successfully deployed we can now update the Keycloak Client with the `Valid redirect URIs`, field must be set to the Cognito Hosted UI domain name with `/oauth2/idresponse` (see below picture for example). then click `Save`.

![sample](./assets/cognito-keycloak-oidc.png "Keycloak Cognito SAML client")

You may also want to map metadata attributes such as email, firstname, lastname etc. however this is not covered in this guide.
24 changes: 24 additions & 0 deletions docs/documentation/cognito/keycloak-saml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Keycloak and Cognito SAML integration

[Keycloak](https://github.com/keycloak/keycloak) describes itself as:

> Open Source Identity and Access Management For Modern Applications and Services
This is a short guide on how to configure OIDC federation between cognito and keycloak. This guide is not a recommendation for keycloak and assumes a certain level of knowledge and familiarity with it.

This guide is for example purposes only and additional configuration may be required to meet your security/integration requirements.

1. You will need to deploy the chatbot after finishing its confiugration before creating the SAML client within Keycloak.
2. See details on [Cognito Overview](./overview.md) for the configuration options for federation
3. You will need to supply the SAML metadata endpoint for keycloak to the installer however. In Keycloak this is found under Realm Settings for the KeycLoak Realm you will create the client under. For example: `https://<keycloak-domain>/realms/<realm-name>/protocol/saml/descriptor`
4. Once the chatbot has been successfully deployed you can now create a keycloak SAML client.
5. Under the desired realm within keycloak navigate to clients and create a new client using the `create client` button and select `SAML`.
6. There are only 2 mandatory fields to get basic functionality with Cognito and that is `Client ID` and `Valid redirect URIs`
7. The `Client ID` field must be set to the cognito user pool ID for example `ap-southeast-2_5fB2IORep` then click `next`
8. The `Valid redirect URIs` field must be set to the Cognito Hosted UI domain name with `/saml2/idresponse` (see below picture for example). then click `Save`saved
9. Keycloak and Cognito both have detailed configuration options available and consideration should be given to what would work for your environment.
10. Once completed you should be able to see a client thats been created within Keycloak that looks like the below:

![sample](./assets/cognito-keycloak-saml.png "Keycloak Cognito SAML client")

You may also want to map metadata attributes such as email, firstname, lastname etc. however this is not covered in this guide.
69 changes: 69 additions & 0 deletions docs/documentation/cognito/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Cognito Federation Overview

This feature when enabled allows you to enable cognito federated login via a custom OIDC or SAML provider.

Examples of this would be wanting to enable your users to login via SAML from an ADFS server, OIDC/SAML with Entra ID, Keycloak, Okta, Auth0 and more.

At a high level you can see the architecture here:

![sample](./assets/scenario-authentication-cup.png "Architecture Diagram")

You can learn more about how federated sign-in works in Amazon Cognito user pools in the AWS docs [here](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html).

## Configuration options

Configuration options in the installer made available by this feature are as follows:

1. "Do you want to enable Federated (SSO) login with Cognito?" => `true/false`

> This turns on or off cognito federation, when false the solution falls back to local cognito user pool managed users.
2. "Please enter the name of the SAML/OIDC Federated identity provider that is or will be setup in Cognito" => `string`

> This is the name of the federation provider created and is also the name of the button users will see on the logon page.
3. "Do you want to setup a SAML or OIDC provider? or choose to do this later after install" => `[SAML, OIDC, Later]`

> Here you can choose to setup a SAML or OIDC provider, alternatively you can choose to configure the provider yourself within Cognito after deployment. This is useful when you have specific requirements that arent supported in the installer or dont have the details yet. This will setup the chatbot with federation enabled and you will just need to ensure when you set it up in Cognito that you do so with the same name as provided above.
4. [SAML] "Provide a URL to a SAML metadata document. This document is issued by your SAML provider." => `URL`

> Here is where you provide a link to the metadata document from your SAML provider. With a Keycloak provider the url looks like this *https://\<keycloak-server\>/realms/\<realm-name\>/protocol/saml/descriptor* with Entra ID it looks like this *https://login.microsoftonline.com/<TenantDomainName\>/FederationMetadata/2007-06/FederationMetadata.xml*
5. [OIDC] "Enter the client ID provided by OpenID Connect identity provider." => `string`

> Provide the OIDC client ID that should be used
6. [OIDC] "Enter the secret manager ARN containing the OIDC client secret to use (see docs for info)" => `Secrets Manager ARN`

> Provide the ARN of the secret within secrets manager containing a plaintext only value of the OIDC Client Secret to be used. By setting it within secrets manager this ensures the secret isnt stored in plain text configuration files or within CloudFormation. Example ARN: *arn:aws:secretsmanager:ap-southeast-2:111504783555:secret:cb-llm-cognito-federation-oidc-gr67hN*
![example](./assets/cognito-example-secret.png "OIDC Secret Example")

7. [OIDC] "Enter the issuer URL you received from the OIDC provider." => `string`

> Enter the issuer URL for example with keycloak this looks like https://\<keycloak-server\>/realms/\<realm-name\>/
8. "Would you like to automatically redirect users to this identity provider?" => `true/false`

> If you only expect users to login via the federated provider and dont need local cognito login as well, you can set this to true to automatically redirect users to login via the federated provider when visiting the webpage. Preventing them from need to click login on the logon page.
## Additional configuration

At this time we havent built in specifing attribute mapping between the federated provider and cognito, you can still do this but it must be setup post deployment within the cognito console.

## Limitations

- We support only 1 federation provider plus cognito login. You cannot use multiple Federation providers at this time.
- We only support OIDC issuer URL and not supplying a discrete URL for each component of the OIDC workflow.
- We only support SAML metadata retreival via URL and not supplying SAML metadata via files.

## Provider Guides

We have some guides available that show how to setup the provider side of the federation configuration.

- [Keycloak OIDC setup](./keycloak-oidc.md)
- [Keycloak SAML setup](./keycloak-saml.md)


## OIDC Client Secret setup via Secrets Manager
Loading

0 comments on commit 564f1e0

Please sign in to comment.