diff --git a/web/packages/design/src/SVGIcon/AWS.tsx b/web/packages/design/src/SVGIcon/AWS.tsx
new file mode 100644
index 0000000000000..4a175a96bc682
--- /dev/null
+++ b/web/packages/design/src/SVGIcon/AWS.tsx
@@ -0,0 +1,48 @@
+/*
+Copyright 2023 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+
+import type { SVGIconProps } from './common';
+
+export function AWSIcon({ size = 24, fill = 'white' }: SVGIconProps) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/web/packages/design/src/SVGIcon/index.ts b/web/packages/design/src/SVGIcon/index.ts
index 586315c0ab7b3..82d788b83b1ab 100644
--- a/web/packages/design/src/SVGIcon/index.ts
+++ b/web/packages/design/src/SVGIcon/index.ts
@@ -20,6 +20,7 @@ export { AddIcon } from './Add';
export { ApplicationsIcon } from './Applications';
export { AuditLogIcon } from './AuditLog';
export { AuthConnectorsIcon } from './AuthConnectors';
+export { AWSIcon } from './AWS';
export { LockIcon } from './Lock';
export { ChevronRightIcon } from './ChevronRight';
export { DatabasesIcon } from './Databases';
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.story.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.story.tsx
new file mode 100644
index 0000000000000..6061b23c53f30
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.story.tsx
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { MemoryRouter } from 'react-router';
+
+import { createTeleportContext } from 'teleport/mocks/contexts';
+import { ContextProvider } from 'teleport';
+
+import { AwsOidc } from './AwsOidc';
+
+export default {
+ title: 'Teleport/Integrations/Enroll/AwsOidc',
+};
+
+export const Flow = () => (
+
+
+
+);
+
+const Provider = props => {
+ const ctx = createTeleportContext({ customAcl: props.customAcl });
+
+ return (
+
+ {props.children}
+
+ );
+};
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx
new file mode 100644
index 0000000000000..ae33d0a6ddcc2
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx
@@ -0,0 +1,361 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useCallback, useEffect, useState } from 'react';
+import styled from 'styled-components';
+
+import { SwitchTransition, Transition } from 'react-transition-group';
+
+import { Header, HeaderSubtitle } from 'teleport/Discover/Shared';
+import { Browser } from 'teleport/Integrations/Enroll/AwsOidc/browser/Browser';
+import { IAMHomeScreen } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMHomeScreen';
+import { Cursor } from 'teleport/Integrations/Enroll/AwsOidc/browser/Cursor';
+import { IAMIdentityProvidersScreen } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMIdentityProvidersScreen';
+import { IAMNewProviderScreen } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMNewProviderScreen';
+import { FirstStageInstructions } from 'teleport/Integrations/Enroll/AwsOidc/instructions/FirstStageInstructions';
+import { SecondStageInstructions } from 'teleport/Integrations/Enroll/AwsOidc/instructions/SecondStageInstructions';
+
+import { ThirdStageInstructions } from 'teleport/Integrations/Enroll/AwsOidc/instructions/ThirdStageInstructions';
+import { IAMProvider } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMProvider';
+
+import { IAMCreateNewRole } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRole';
+import { FourthStageInstructions } from 'teleport/Integrations/Enroll/AwsOidc/instructions/FourthStageInstructions';
+import { IAMCreateNewRolePermissions } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRolePermissions';
+import { FifthStageInstructions } from 'teleport/Integrations/Enroll/AwsOidc/instructions/FifthStageInstructions';
+import { IAMCreateNewPolicy } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewPolicy';
+import { SixthStageInstructions } from 'teleport/Integrations/Enroll/AwsOidc/instructions/SixthStageInstructions';
+
+import { SeventhStageInstructions } from 'teleport/Integrations/Enroll/AwsOidc/instructions/SeventhStageInstructions';
+import { IAMRoles } from 'teleport/Integrations/Enroll/AwsOidc/IAM/IAMRoles';
+import useTeleport from 'teleport/useTeleport';
+
+import { Stage, STAGES } from './stages';
+
+const Container = styled.div`
+ padding-right: 40px;
+ padding-top: 16px;
+`;
+
+const InstructionsContainer = styled.div`
+ display: flex;
+ margin-top: 50px;
+`;
+
+const BrowserContainer = styled.div`
+ position: relative;
+`;
+
+const RestartAnimation = styled.div`
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ opacity: ${p => (p.visible ? 1 : 0)};
+ transition: 0.2s ease-in-out opacity;
+ justify-content: center;
+ position: absolute;
+ bottom: 10px;
+ background: rgba(0, 0, 0, 0.8);
+ border-radius: 5px;
+ padding: 5px 10px;
+ cursor: pointer;
+ left: 50%;
+ transform: translate(-50%, 0);
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
+
+ &:hover {
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
+ }
+`;
+
+const defaultStyle = {
+ transition: 'opacity 250ms, transform 250ms',
+ opacity: 0,
+ width: '100%',
+};
+
+const horizontalTransitionStyles = {
+ entering: { opacity: 0, transform: 'translateX(50px)' },
+ entered: { opacity: 1, transform: 'translateX(0%)' },
+ exiting: { opacity: 0, transform: 'translateX(-50px)' },
+ exited: { opacity: 0, transform: 'translateX(-50px)' },
+};
+
+enum InstructionStep {
+ First,
+ Second,
+ Third,
+ Fourth,
+ Fifth,
+ Sixth,
+ Seventh,
+}
+
+export function AwsOidc() {
+ const ctx = useTeleport();
+ let clusterPublicUri = ctx.storeUser.state.cluster.publicURL;
+
+ const [stage, setStage] = useState(Stage.Initial);
+ const [showRestartAnimation, setShowRestartAnimation] = useState(false);
+
+ const currentStageIndex = STAGES.findIndex(s => s.kind === stage);
+ const currentStage = STAGES[currentStageIndex];
+ const currentStageConfig = getStageConfig(stage);
+
+ const restartAnimation = useCallback(() => {
+ setStage(currentStageConfig.restartStage);
+ setShowRestartAnimation(false);
+ }, [currentStageConfig]);
+
+ useEffect(() => {
+ if (currentStage.end) {
+ setShowRestartAnimation(true);
+
+ return;
+ }
+
+ if (showRestartAnimation) {
+ setShowRestartAnimation(false);
+ }
+
+ if (currentStage.duration && STAGES[currentStageIndex + 1]) {
+ const id = window.setTimeout(
+ () => setStage(STAGES[currentStageIndex + 1].kind),
+ currentStage.duration
+ );
+
+ return () => window.clearTimeout(id);
+ }
+ }, [currentStage, currentStageIndex, showRestartAnimation]);
+
+ return (
+
+
+
+
+ Instead of storing long-lived static credentials, Teleport will become a
+ trusted OIDC provider with AWS to be able to request short lived
+ credentials when performing operations automatically.
+
+
+
+
+
+ {state => (
+
+ {currentStageConfig.instructionStep ===
+ InstructionStep.First && (
+ {
+ setStage(Stage.NewProviderFullScreen);
+ }}
+ clusterPublicUri={clusterPublicUri}
+ />
+ )}
+ {currentStageConfig.instructionStep ===
+ InstructionStep.Second && (
+ {
+ setStage(Stage.AddProvider);
+ }}
+ clusterPublicUri={clusterPublicUri}
+ />
+ )}
+ {currentStageConfig.instructionStep ===
+ InstructionStep.Third && (
+ {
+ setStage(Stage.CreateNewRole);
+ }}
+ clusterPublicUri={clusterPublicUri}
+ />
+ )}
+ {currentStageConfig.instructionStep ===
+ InstructionStep.Fourth && (
+ {
+ setStage(Stage.CreatePolicy);
+ }}
+ clusterPublicUri={clusterPublicUri}
+ />
+ )}
+ {currentStageConfig.instructionStep ===
+ InstructionStep.Fifth && (
+ {
+ setStage(Stage.AssignPolicyToRole);
+ }}
+ clusterPublicUri={clusterPublicUri}
+ />
+ )}
+ {currentStageConfig.instructionStep ===
+ InstructionStep.Sixth && (
+ {
+ setStage(Stage.ListRoles);
+ }}
+ clusterPublicUri={clusterPublicUri}
+ />
+ )}
+ {currentStageConfig.instructionStep ===
+ InstructionStep.Seventh && }
+
+ )}
+
+
+
+
+
+
+ {getStageComponent(stage, clusterPublicUri)}
+
+
+ restartAnimation()}
+ >
+ Restart animation
+
+
+
+
+ );
+}
+
+function getStageComponent(stage: Stage, uri: string) {
+ let clusterPublicUri = uri;
+ // Truncate long URI's so it doesn't mess up the animation screens.
+ if (clusterPublicUri.length > 30) {
+ clusterPublicUri = `${clusterPublicUri.substring(0, 30)}...`;
+ }
+ const props = { stage, clusterPublicUri };
+
+ if (stage >= Stage.Initial && stage <= Stage.ClickIdentityProviders) {
+ return ;
+ }
+
+ if (stage >= Stage.IdentityProviders && stage <= Stage.ClickAddProvider) {
+ return ;
+ }
+
+ if (stage >= Stage.NewProvider && stage <= Stage.AddProvider) {
+ return ;
+ }
+
+ if (stage >= Stage.ProviderAdded && stage <= Stage.SelectProvider) {
+ return ;
+ }
+
+ if (stage >= Stage.ProviderView && stage <= Stage.ClickCreateNewRole) {
+ return ;
+ }
+
+ if (stage >= Stage.CreateNewRole && stage <= Stage.ClickNextPermissions) {
+ return ;
+ }
+
+ if (
+ stage >= Stage.ConfigureRolePermissions &&
+ stage <= Stage.ClickCreatePolicy
+ ) {
+ return ;
+ }
+
+ if (stage >= Stage.CreatePolicy && stage <= Stage.ClickCreatePolicyButton) {
+ return ;
+ }
+
+ if (
+ stage >= Stage.AssignPolicyToRole &&
+ stage <= Stage.ClickCreateRoleButton
+ ) {
+ return ;
+ }
+
+ if (stage >= Stage.ListRoles) {
+ return ;
+ }
+}
+
+function getStageConfig(stage: Stage) {
+ if (stage >= Stage.Initial && stage <= Stage.NewProvider) {
+ return {
+ instructionStep: InstructionStep.First,
+ restartStage: Stage.Initial,
+ };
+ }
+
+ if (
+ stage >= Stage.NewProviderFullScreen &&
+ stage <= Stage.ThumbprintSelected
+ ) {
+ return {
+ instructionStep: InstructionStep.Second,
+ restartStage: Stage.NewProviderFullScreen,
+ };
+ }
+
+ if (stage >= Stage.AddProvider && stage <= Stage.ClickCreateNewRole) {
+ return {
+ instructionStep: InstructionStep.Third,
+ restartStage: Stage.AddProvider,
+ };
+ }
+
+ if (stage >= Stage.CreateNewRole && stage <= Stage.ClickCreatePolicy) {
+ return {
+ instructionStep: InstructionStep.Fourth,
+ restartStage: Stage.CreateNewRole,
+ };
+ }
+
+ if (stage >= Stage.CreatePolicy && stage <= Stage.ClickCreatePolicyButton) {
+ return {
+ instructionStep: InstructionStep.Fifth,
+ restartStage: Stage.CreatePolicy,
+ };
+ }
+
+ if (
+ stage >= Stage.AssignPolicyToRole &&
+ stage <= Stage.ClickCreateRoleButton
+ ) {
+ return {
+ instructionStep: InstructionStep.Sixth,
+ restartStage: Stage.AssignPolicyToRole,
+ };
+ }
+
+ if (stage >= Stage.ListRoles) {
+ return {
+ instructionStep: InstructionStep.Seventh,
+ restartStage: Stage.ListRoles,
+ };
+ }
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/AssignRoleModal.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/AssignRoleModal.tsx
new file mode 100644
index 0000000000000..2a34953847fea
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/AssignRoleModal.tsx
@@ -0,0 +1,171 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import { AddIcon } from 'design/SVGIcon';
+
+import { NextButton } from './common';
+
+const Modal = styled.div`
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ z-index: 9;
+ background: rgba(0, 0, 0, 0.4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const AssignRole = styled.div<{ active: boolean }>`
+ border-radius: 4px;
+ border: 1px solid #bdbdbd;
+ overflow: hidden;
+ width: 500px;
+`;
+
+const AssignRoleClose = styled.div`
+ transform: rotate(45deg);
+ position: relative;
+ top: 2px;
+`;
+
+const AssignRoleHeader = styled.div`
+ background: #eee;
+ display: flex;
+ justify-content: space-between;
+ font-size: 16px;
+ padding: 10px 20px;
+ border-bottom: 1px solid #dddddd;
+`;
+
+const AssignRoleFooter = styled.div`
+ background: #eee;
+ display: flex;
+ justify-content: flex-end;
+ font-size: 16px;
+ padding: 10px 20px;
+ border-top: 1px solid #dddddd;
+`;
+
+const AssignRoleContent = styled.div`
+ background: white;
+ padding: 10px 20px;
+ display: flex;
+ justify-content: space-between;
+`;
+
+const AssignRoleOption = styled.div<{ active: boolean }>`
+ display: flex;
+ background: ${p => (p.active ? '#f0f9ff' : 'white')};
+ border: 1px solid ${p => (p.active ? '#99c2ee' : '#e6e6e6')};
+ border-radius: 4px;
+ flex: 0 0 200px;
+ padding: 8px 12px 10px;
+`;
+
+const AssignRoleOptionBulletContainer = styled.div`
+ flex: 0 0 25px;
+ padding: 4px 0;
+`;
+
+const AssignRoleOptionBullet = styled.div<{ active: boolean }>`
+ width: 14px;
+ height: 14px;
+ background: ${p => (p.active ? '#1066bb' : '#cccccc')};
+ border-radius: 50%;
+ position: relative;
+ &:after {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: ${p => (p.active ? 6 : 11)}px;
+ height: ${p => (p.active ? 6 : 11)}px;
+ content: '';
+ background: white;
+ border-radius: 50%;
+ }
+`;
+
+const CancelButton = styled.div`
+ color: #1869bb;
+ padding: 5px 15px;
+ font-size: 14px;
+ font-weight: 700;
+ margin-right: 10px;
+`;
+
+const AssignRoleOptionDescription = styled.div`
+ font-size: 12px;
+ line-height: 1.2;
+ color: #999;
+`;
+
+export function AssignRoleModal({
+ clusterPublicUri,
+}: {
+ clusterPublicUri: string;
+}) {
+ return (
+
+
+
+
+ Assign role for {clusterPublicUri}
+
+
+
+
+
+
+
+
+
+
+
Create a new role
+
+ Create a new role for this identity provider and add
+ permissions to the new role.
+
+
+
+
+
+
+
+
+
Use an existing role
+
+ Associate an existing role by adding this IdP to the trusted
+ entities of the role.
+
+
+
+
+
+ Cancel
+ Next
+
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewPolicy.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewPolicy.tsx
new file mode 100644
index 0000000000000..a5f30b90e3838
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewPolicy.tsx
@@ -0,0 +1,206 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import Flex from 'design/Flex';
+import Text from 'design/Text';
+
+import { Stage } from '../stages';
+
+import { AWSWrapper } from './SharedComponents';
+
+import {
+ Content,
+ Footer,
+ Header,
+ NextButton,
+ Page,
+ RoleButton,
+ Section,
+ SectionContent,
+ SectionDropdown,
+ SectionDropdownSelected,
+ SectionTitle,
+} from './common';
+
+import type { CommonIAMProps } from './common';
+
+const Tabs = styled.div`
+ display: flex;
+ border-bottom: 1px solid #ccc;
+`;
+
+const Tab = styled.div<{ active: boolean }>`
+ background: ${p => (p.active ? 'white' : '#eeeeee')};
+ font-weight: bold;
+ border: 1px solid #cccccc;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ padding: 5px 15px;
+ margin-right: 10px;
+ position: relative;
+ overflow: hidden;
+ border-top-width: ${p => (p.active ? 0 : '1px')};
+ border-bottom-color: ${p => (p.active ? 'white' : '#cccccc')};
+ margin-bottom: -1px;
+
+ &:after {
+ height: 3px;
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ background: #e07701;
+ content: '';
+ display: ${p => (p.active ? 'block' : 'none')};
+ }
+`;
+
+const JSONEditor = styled.div<{ selected: boolean }>`
+ border: 1px solid #ccc;
+ margin-top: 20px;
+ padding: 0 25px;
+ font-size: 14px;
+ position: relative;
+
+ &:after {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ content: '';
+ background: #b5d5ff;
+ z-index: 0;
+ display: ${p => (p.selected ? 'block' : 'none')};
+ }
+
+ pre {
+ position: relative;
+ z-index: 1;
+ }
+`;
+
+export function IAMCreateNewPolicy(props: CommonIAMProps) {
+ let jsonEditor;
+ if (props.stage >= Stage.ShowJSONEditor) {
+ let content = `{
+ "Version": "2012-10-17",
+ "Statement": []
+}`;
+ if (props.stage >= Stage.PolicyJSONPasted) {
+ content = `{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": "rds:DescribeDBInstances",
+ "Resource": "*"
+ }
+ ]
+}`;
+ }
+
+ jsonEditor = (
+
+ {content}
+
+ );
+ }
+
+ let content;
+ if (props.stage <= Stage.PolicyClickNextTags) {
+ content = (
+ <>
+
+ Visual Editor
+ = Stage.ShowJSONEditor}>JSON
+
+
+ {jsonEditor}
+
+
+ >
+ );
+ }
+
+ if (
+ props.stage >= Stage.PolicyTags &&
+ props.stage <= Stage.PolicyClickNextReview
+ ) {
+ content = (
+ <>
+
+ Add tags - optional
+
+
+
+ Add tag
+
+
+
+ >
+ );
+ }
+
+ if (props.stage >= Stage.PolicyReview) {
+ content = (
+ <>
+
+ Review policy
+
+
+
+ Name*
+
+
+
+
+ {props.stage >= Stage.PolicyHasName ? (
+ 'SomePolicyName'
+ ) : (
+ <> >
+ )}
+
+
+
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+
+
+
+ {content}
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRole.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRole.tsx
new file mode 100644
index 0000000000000..387b25caf183a
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRole.tsx
@@ -0,0 +1,168 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import { Stage } from '../stages';
+
+import { AWSWrapper } from './SharedComponents';
+
+import {
+ Content,
+ Footer,
+ Header,
+ NextButton,
+ Page,
+ Section,
+ SectionContent,
+ SectionDropdown,
+ SectionDropdownSelected,
+ SectionTitle,
+ SubHeader,
+} from './common';
+
+import type { CommonIAMProps } from './common';
+
+const TrustedEntities = styled.div`
+ display: flex;
+ margin-bottom: 30px;
+`;
+
+const TrustedEntity = styled.div<{ active: boolean }>`
+ background: ${p => (p.active ? '#e6f3ff' : 'white')};
+ border: 2px solid ${p => (p.active ? '#1066bb' : '#cccccc')};
+ flex: 0 0 180px;
+ margin-right: 10px;
+ display: flex;
+`;
+
+const TrustedEntityContent = styled.div`
+ padding: 10px 15px;
+`;
+
+const TrustedEntityTitle = styled.div`
+ font-weight: bold;
+`;
+
+const TrustedEntityDescription = styled.div`
+ font-size: 12px;
+ color: #666;
+ line-height: 1.2;
+`;
+
+const SectionDropdownItems = styled.div`
+ background: #222222;
+ position: absolute;
+ top: 34px;
+ color: white;
+ width: 300px;
+ border-radius: 4px;
+`;
+
+const SectionDropdownItem = styled.div<{ hovered: boolean }>`
+ padding: 5px 15px;
+ color: ${p => (p.hovered ? '#d27106' : 'white')};
+`;
+
+export function IAMCreateNewRole(props: CommonIAMProps) {
+ return (
+
+
+
+
+
+ Select type of trusted entity
+
+
+
+
+ AWS service
+
+
+ EC2, Lambda and others
+
+
+
+
+
+
+ Web identity
+
+
+ Cognito or any OpenID provider
+
+
+
+
+
+
+ SAML 2.0 federation
+
+
+ Your corporate directory
+
+
+
+
+
+ Choose a web identity provider
+
+
+ Identity Provider
+
+
+
+
+ {props.clusterPublicUri}:aud
+
+
+
+
+
+
+ Audience*
+
+
+
+
+ {props.stage >= Stage.DiscoverAudienceSelected ? (
+ 'discover.teleport'
+ ) : (
+ <> >
+ )}
+
+
+ {props.stage >= Stage.ShowAudienceDropdown &&
+ props.stage < Stage.DiscoverAudienceSelected && (
+
+
+ discover.teleport
+
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRolePermissions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRolePermissions.tsx
new file mode 100644
index 0000000000000..69f67c1aec9d1
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMCreateNewRolePermissions.tsx
@@ -0,0 +1,195 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import Text from 'design/Text';
+import Flex from 'design/Flex';
+
+import { Stage } from '../stages';
+
+import { AWSWrapper } from './SharedComponents';
+
+import {
+ Content,
+ Footer,
+ Header,
+ NextButton,
+ Page,
+ RoleButton,
+ Section,
+ SectionContent,
+ SectionDropdown,
+ SectionDropdownSelected,
+ SectionTitle,
+ SubHeader,
+ TableCheckBox,
+ TableHeader,
+ TableItem,
+ TableSearch,
+ TableTitle,
+} from './common';
+
+import type { CommonIAMProps } from './common';
+
+const CreatePolicyButton = styled.div`
+ display: flex;
+ justify-content: space-between;
+`;
+
+const Policies = styled.div`
+ margin-top: 20px;
+`;
+
+export function IAMCreateNewRolePermissions(props: CommonIAMProps) {
+ let content = (
+ <>
+ Attach permissions policies
+
+
+ Create policy
+
+
+
+ >
+ );
+
+ if (props.stage >= Stage.RoleReview) {
+ content = (
+ <>
+
+ Review
+
+
+
+ Role Name*
+
+
+
+
+ {props.stage >= Stage.RoleHasName ? (
+ 'SomeRoleName'
+ ) : (
+ <> >
+ )}
+
+
+
+
+
+
+ >
+ );
+ }
+
+ if (
+ props.stage >= Stage.RoleTags &&
+ props.stage <= Stage.RoleClickNextReview
+ ) {
+ content = (
+ <>
+
+ Add tags - optional
+
+
+
+ Add tag
+
+
+
+ >
+ );
+ }
+
+ if (
+ props.stage >= Stage.AssignPolicyToRole &&
+ props.stage <= Stage.RoleClickNextTags
+ ) {
+ content = (
+ <>
+ Attach permissions policies
+
+
+ Create policy
+
+ Refresh
+
+
+
+
+
+ {props.stage >= Stage.SearchForPolicy
+ ? 'SomePolicyName'
+ : 'Search'}
+
+
+ {props.stage >= Stage.SearchForPolicy ? (
+
+
Policy Name
+
= Stage.PolicySelected}>
+
+ SomePolicyName
+
+
+ ) : (
+
+
Policy Name
+
+
+ AWSDefaultPolicy
+
+
+
+ AWSDefaultPolicy2
+
+
+
+ AWSDefaultPolicy3
+
+
+
+ AWSDefaultPolicy4
+
+
+ )}
+
+
+
+ >
+ );
+ }
+
+ return (
+
+
+
+
+
+ {content}
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMHomeScreen.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMHomeScreen.tsx
new file mode 100644
index 0000000000000..d270fd3e16996
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMHomeScreen.tsx
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+import Text from 'design/Text';
+import Flex from 'design/Flex';
+import Box from 'design/Box';
+
+import { AWSWrapper } from './SharedComponents';
+
+import {
+ Blur,
+ Content,
+ Page,
+ Sidebar,
+ SidebarLink,
+ SidebarLinkActive,
+ SidebarSectionTitle,
+ SidebarTitle,
+ Title,
+} from './common';
+
+export function IAMHomeScreen() {
+ return (
+
+
+
+ Identity and Access Management (IAM)
+
+ Dashboard
+
+ Access Management
+
+ User groups
+ Users
+ Roles
+
+ Identity providers
+
+ Account settings
+
+ Access reports
+
+ Access analyzer
+ Credential report
+ Organization activity
+
+
+ IAM Dashboard
+
+
+ Some blurred text here. Some other text here.
+ Some blurred text here
+ IAM Resources
+
+
+ User groups
+
+ 10
+
+
+
+ Users
+
+ 145
+
+
+
+ Identity providers
+
+ 0
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMIdentityProvidersScreen.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMIdentityProvidersScreen.tsx
new file mode 100644
index 0000000000000..31901992b5f6e
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMIdentityProvidersScreen.tsx
@@ -0,0 +1,145 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import Flex from 'design/Flex';
+
+import { Stage } from '../stages';
+
+import { AWSWrapper, BreadcrumbArrow } from './SharedComponents';
+
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbItemActive,
+ Content,
+ NextButton,
+ Page,
+ Sidebar,
+ SidebarLink,
+ SidebarLinkActive,
+ SidebarSectionTitle,
+ SidebarTitle,
+ Title,
+} from './common';
+
+import type { CommonIAMProps } from './common';
+
+const Providers = styled.div`
+ display: flex;
+ margin-top: 20px;
+ width: calc(100% - 60px);
+`;
+
+const ProviderSection = styled.div`
+ flex: 1;
+`;
+
+const ProviderSectionTitle = styled.div`
+ background: linear-gradient(#eee, #e0e0e0);
+ padding: 5px 10px;
+ font-weight: 700;
+`;
+
+const ProviderTitle = styled.div`
+ padding: 5px 10px;
+ color: #16b;
+`;
+
+const ProviderSectionContent = styled.div`
+ padding: 5px 10px;
+`;
+
+export function IAMIdentityProvidersScreen(props: CommonIAMProps) {
+ if (props.stage >= Stage.ProviderAdded) {
+ return (
+
+
+
+
+ IAM
+
+ Identity providers
+
+
+
+ Identity Providers (1)
+
+
+
+
+ Provider
+
+ {props.clusterPublicUri}
+
+
+ Type
+
+ OpenID Connect
+
+
+ Creation time
+
+ 1 minute ago
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ Identity and Access Management (IAM)
+
+ Dashboard
+
+ Access Management
+
+ User groups
+ Users
+ Roles
+ Identity providers
+ Account settings
+
+ Access reports
+
+ Access analyzer
+ Credential report
+ Organization activity
+
+
+
+ IAM
+
+ Identity providers
+
+
+
+ Identity Providers (0)
+
+ Add provider
+
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMNewProviderScreen.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMNewProviderScreen.tsx
new file mode 100644
index 0000000000000..558517783482d
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMNewProviderScreen.tsx
@@ -0,0 +1,178 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import Box from 'design/Box';
+import Flex from 'design/Flex';
+
+import { Stage } from '../stages';
+
+import { AWSWrapper, BreadcrumbArrow } from './SharedComponents';
+
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbItemActive,
+ Content,
+ Page,
+ Sidebar,
+ SidebarLink,
+ SidebarLinkActive,
+ SidebarSectionTitle,
+ SidebarTitle,
+ Title,
+} from './common';
+
+import { OpenIDForm } from './OpenIDForm';
+
+import type { CommonIAMProps } from './common';
+
+const ProviderType = styled.div<{ active: boolean }>`
+ flex: 0 0 220px;
+ border: 1px solid ${p => (p.active ? '#3388dd' : '#cccccc')};
+ background: ${p => (p.active ? '#f0f9ff' : 'white')};
+ border-radius: 5px;
+ display: flex;
+ margin-right: 10px;
+`;
+
+const ProviderTypeIconContainer = styled.div`
+ flex: 0 0 30px;
+ display: flex;
+ justify-content: center;
+ padding-top: 12px;
+`;
+
+const ProviderTypeIcon = styled.div`
+ width: 10px;
+ height: 10px;
+ background: ${p => (p.active ? '#1066bb' : 'white')};
+ border: 1px solid ${p => (p.active ? '#1066bb' : '#cccccc')};
+ border-radius: 50%;
+ position: relative;
+
+ &:after {
+ content: '';
+ visibility: ${p => (p.active ? 'visible' : 'hidden')};
+ position: absolute;
+ width: 5px;
+ height: 5px;
+ background: white;
+ border-radius: 50%;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+`;
+
+const ProviderTypeInfo = styled.div`
+ padding: 10px 0;
+ font-size: 10px;
+ color: #999;
+ line-height: 12px;
+`;
+
+const ProviderTypeTitle = styled.div`
+ font-size: 14px;
+ line-height: 16px;
+ margin-bottom: 10px;
+ color: #444;
+`;
+
+const SlideOutPage = styled(Page)`
+ transition: 1s transform cubic-bezier(0.4, 0, 0.2, 1);
+ transform: translate3d(${p => (p.hidden ? -251 : 0)}px, 0, 0);
+`;
+
+export function IAMNewProviderScreen(props: CommonIAMProps) {
+ const sidebarHidden = props.stage !== Stage.NewProvider;
+ const openIDSelected = props.stage >= Stage.OpenIDConnectSelected;
+
+ return (
+
+
+
+ Identity and Access Management (IAM)
+
+ Dashboard
+
+ Access Management
+
+ User groups
+ Users
+ Roles
+ Identity providers
+ Account settings
+
+ Access reports
+
+ Access analyzer
+ Credential report
+ Organization activity
+
+
+
+ IAM
+
+ Identity providers
+
+ Create
+
+
+
+ Add an Identity Provider
+
+
+
+
+
+
+
+
+
+ SAML
+ Establish trust between your AWS account and a SAML 2.0
+ compatible Identity Provider such as Shibboleth or Active
+ Directory Federation Services.
+
+
+
+
+
+
+
+
+
+ OpenID Connect
+ Establish trust between your AWS account and Identity Provider
+ services, such as Google or Salesforce.
+
+
+
+
+ {openIDSelected && (
+
+ )}
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMProvider.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMProvider.tsx
new file mode 100644
index 0000000000000..8af2b99067a97
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMProvider.tsx
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import Flex from 'design/Flex';
+import Box from 'design/Box';
+
+import { Stage } from '../stages';
+
+import { AWSWrapper, BreadcrumbArrow } from './SharedComponents';
+
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbItemActive,
+ Content,
+ Page,
+ RoleButton,
+ Title,
+} from './common';
+
+import { AssignRoleModal } from './AssignRoleModal';
+
+import type { CommonIAMProps } from './common';
+
+const AWSHeader = styled.div`
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 10px;
+ display: flex;
+`;
+
+const AWSHeaderCount = styled.div`
+ font-weight: 400;
+ opacity: 0.5;
+ margin-left: 10px;
+`;
+
+const SummaryContent = styled.div`
+ display: flex;
+ border-top: 1px solid #cccccc;
+ padding-top: 10px;
+ margin-bottom: 40px;
+`;
+
+const SummaryItem = styled.div`
+ display: flex;
+ flex-direction: column;
+ min-width: 200px;
+ border-right: 1px solid #ccc;
+ padding-left: 10px;
+
+ &:first-of-type {
+ padding: 0;
+ }
+
+ &:last-of-type {
+ border-right: none;
+ }
+`;
+
+export function IAMProvider(props: CommonIAMProps) {
+ const showAssignRoleModal = props.stage >= Stage.ShowAssignRoleModal;
+
+ return (
+
+
+ {showAssignRoleModal && (
+
+ )}
+
+
+
+ IAM
+
+ Identity providers
+
+
+ {props.clusterPublicUri}
+
+
+
+
+ {props.clusterPublicUri}
+
+ Assign role
+
+
+ Summary
+
+
+
+ Provider
+ {props.clusterPublicUri}
+
+
+ Provider Type
+ OpenID Connect
+
+
+ Creation Time
+ 1 minute ago
+
+
+
+
+
+
+ Audiences (1)
+
+
+
+
+
+ Thumbprints (1)
+
+
+
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMRoles.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMRoles.tsx
new file mode 100644
index 0000000000000..0dd667b5b45af
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/IAMRoles.tsx
@@ -0,0 +1,176 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import { Stage } from '../stages';
+
+import { AWSWrapper } from './SharedComponents';
+
+import { Content, Page } from './common';
+
+import type { CommonIAMProps } from './common';
+
+const RolesSuccess = styled.div`
+ background: #019934;
+ color: white;
+ padding: 10px 15px;
+ font-size: 16px;
+`;
+
+const RolesSuccessLink = styled.span`
+ text-decoration: underline;
+`;
+
+const RolesHeader = styled.div`
+ background-image: linear-gradient(#fff, #eee);
+ padding: 20px 20px;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ font-weight: bold;
+ font-size: 22px;
+`;
+
+const PoliciesTableHeader = styled.div`
+ background-image: linear-gradient(#eee, #e0e0e0);
+ padding: 10px 45px;
+ font-weight: bold;
+ border: 1px solid #ccc;
+ border-top: none;
+`;
+
+const PoliciesCheckBox = styled.div`
+ width: 10px;
+ height: 10px;
+ margin-right: 20px;
+ border-radius: 3px;
+`;
+
+const PoliciesTableItem = styled.div<{ selected?: boolean }>`
+ display: flex;
+ align-items: center;
+ padding: 10px 15px;
+ border-bottom: 1px solid #cccccc;
+ background: ${p => (p.selected ? '#e6f3ff' : 'white')};
+
+ ${PoliciesCheckBox} {
+ background: ${p => (p.selected ? '#1066bb' : 'white')};
+ border: 1px solid ${p => (p.selected ? '#1066bb' : '#cccccc')};
+ }
+`;
+
+const RoleName = styled.div`
+ font-size: 24px;
+`;
+
+const SummaryTitle = styled.div`
+ font-weight: bold;
+ font-size: 18px;
+ margin-top: 30px;
+`;
+
+const Summary = styled.div`
+ margin-top: 20px;
+ border-top: 1px solid #ccc;
+ display: flex;
+`;
+
+const SummarySection = styled.div`
+ margin-top: 5px;
+ flex: 1;
+ padding-top: 10px;
+`;
+
+const SectionLabel = styled.div`
+ font-size: 14px;
+ color: rgba(0, 0, 0, 0.6);
+`;
+
+const SectionValue = styled.div`
+ font-size: 16px;
+`;
+
+export function IAMRoles(props: CommonIAMProps) {
+ let content;
+ if (props.stage >= Stage.ListRoles && props.stage <= Stage.ClickRole) {
+ content = (
+
+
+ The role SomeRoleName has been
+ created
+
+
+
+
Roles
+
+
+
Role Name
+
+
+ AWSDefaultRole
+
+
+
+ AWSDefaultRole2
+
+
+
+ AWSDefaultRole3
+
+
+
+ AWSDefaultRole4
+
+
+
+
+
+ );
+ }
+
+ if (props.stage >= Stage.ViewRole) {
+ content = (
+
+ SomeRoleName
+
+ Summary
+
+
+
+ Creation Date
+ Just now
+
+ Lst activity
+ None
+
+
+ ARN
+
+ arn:aws:iam::123456789:role/SomeRoleName
+
+
+
+
+ );
+ }
+
+ return (
+
+ {content}
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/OpenIDForm.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/OpenIDForm.tsx
new file mode 100644
index 0000000000000..d76130b9c7aba
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/OpenIDForm.tsx
@@ -0,0 +1,148 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import { Stage } from '../stages';
+
+import { NextButton } from './common';
+
+import type { CommonIAMProps } from './common';
+
+const Form = styled.div`
+ margin-top: 20px;
+ display: flex;
+ flex-direction: column;
+`;
+
+const InputContainer = styled.div`
+ display: flex;
+ margin-bottom: 20px;
+`;
+
+const Button = styled.div`
+ background: linear-gradient(#fff, #dedede);
+ border: 1px solid #b8b8b8;
+ color: #444;
+ padding: 3px 7px;
+ border-radius: 5px;
+ font-weight: 700;
+ height: 32px;
+ box-sizing: border-box;
+`;
+
+const Input = styled.div`
+ font-size: 14px;
+ border-radius: 5px;
+ border: 1px solid #ccc;
+ padding: 3px 7px;
+ height: 32px;
+ box-sizing: border-box;
+ width: 300px;
+ margin-right: 15px;
+ background: ${p => (p.disabled ? '#eeeeee' : 'white')};
+`;
+
+const ThumbprintContainer = styled.div`
+ background: #eeeeee;
+ border: 1px solid #cccccc;
+ padding: 7px 10px;
+ display: flex;
+ width: 400px;
+ margin-bottom: 20px;
+`;
+
+const ThumbprintSection = styled.div`
+ margin-right: 60px;
+`;
+
+const Thumbprint = styled.span`
+ background: ${p => (p.selected ? '#add0f7' : 'none')};
+`;
+
+const Buttons = styled.div`
+ display: flex;
+ justify-content: flex-end;
+ width: calc(100% - 120px);
+ margin-top: 20px;
+`;
+
+export function OpenIDForm(props: CommonIAMProps) {
+ const providerURL =
+ props.stage >= Stage.PastedProviderURL
+ ? `https://${props.clusterPublicUri}`
+ : 'https://';
+ const audience =
+ props.stage >= Stage.PastedAudience ? 'discover.teleport' : '';
+
+ let providerDisabled = false;
+ let buttonText = 'Get thumbprint';
+ if (props.stage >= Stage.ThumbprintLoading) {
+ buttonText = 'Loading...';
+ }
+ if (props.stage >= Stage.ThumbprintResult) {
+ buttonText = 'Edit URL';
+ providerDisabled = true;
+ }
+
+ let fingerprintResult;
+ if (props.stage >= Stage.ThumbprintResult) {
+ fingerprintResult = (
+
+
Verify thumbprint
+
+
+
+ Thumbprint
+
+
+ examplevaluehere
+
+
+
+ Issued By
+ Example Inc
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/SharedComponents.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/SharedComponents.tsx
new file mode 100644
index 0000000000000..14551a14145ce
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/SharedComponents.tsx
@@ -0,0 +1,76 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import { AWSIcon, ChevronRightIcon } from 'design/SVGIcon';
+
+import useTeleport from 'teleport/useTeleport';
+
+import { BreadcrumbIconContainer } from './common';
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 630px;
+ overflow: hidden;
+`;
+
+const Header = styled.div`
+ background: #232e3e;
+ height: 32px;
+ padding: 0 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+const HeaderLogo = styled.div`
+ height: 25px;
+`;
+
+const HeaderUsername = styled.div`
+ color: white;
+ font-size: 12px;
+`;
+
+export function BreadcrumbArrow() {
+ return (
+
+
+
+ );
+}
+
+export function AWSWrapper(props: React.PropsWithChildren) {
+ const ctx = useTeleport();
+
+ return (
+
+
+
+
+
+
+ {ctx.storeUser.state.username}
+
+
+ {props.children}
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/common.ts b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/common.ts
new file mode 100644
index 0000000000000..710e3fe644a08
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/IAM/common.ts
@@ -0,0 +1,222 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import styled from 'styled-components';
+
+import { Stage } from '../stages';
+
+export interface CommonIAMProps {
+ stage: Stage;
+ clusterPublicUri: string;
+}
+
+export const Header = styled.div`
+ font-size: 22px;
+ margin-bottom: 20px;
+ display: flex;
+`;
+
+export const Footer = styled.div`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 46px;
+ border-top: 1px solid #dcdcdc;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding-right: 30px;
+`;
+
+export const Page = styled.div`
+ display: flex;
+ flex: 1;
+`;
+
+export const Sidebar = styled.div`
+ display: flex;
+ flex-direction: column;
+ border-right: 1px solid rgba(0, 0, 0, 0.1);
+ flex: 0 0 250px;
+ width: 250px;
+ height: inherit;
+`;
+
+export const SidebarSectionTitle = styled.div`
+ font-weight: 700;
+ color: rgba(0, 0, 0, 0.5);
+ padding: 0 20px;
+ margin-top: 15px;
+ margin-bottom: 5px;
+`;
+
+export const SidebarTitle = styled.div`
+ font-size: 18px;
+ padding: 20px 20px;
+ font-weight: bold;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.07);
+ margin-bottom: 20px;
+`;
+
+export const SidebarLink = styled.div`
+ padding: 0 20px;
+ margin-bottom: 5px;
+ position: relative;
+ z-index: 2;
+`;
+
+export const SidebarLinkActive = styled(SidebarLink)`
+ color: #0073bb;
+ font-weight: 500;
+`;
+
+export const Content = styled.div`
+ padding: 30px;
+ box-sizing: border-box;
+ flex: 0 0 630px;
+ width: 630px;
+`;
+
+export const Title = styled.div`
+ font-size: 18px;
+ margin-bottom: 30px;
+`;
+
+export const Blur = styled.div`
+ filter: blur(5px);
+ width: 100%;
+`;
+
+export const Breadcrumb = styled.div`
+ display: flex;
+`;
+
+export const BreadcrumbItem = styled.div`
+ color: #0073bb;
+`;
+
+export const BreadcrumbItemActive = styled(BreadcrumbItem)`
+ font-weight: 700;
+ color: #687078;
+`;
+
+export const BreadcrumbIconContainer = styled.div`
+ margin: 0 10px;
+`;
+
+export const NextButton = styled.div`
+ background: linear-gradient(#2c8bea, #1267bc);
+ color: white;
+ padding: 5px 15px;
+ font-size: 14px;
+ font-weight: 700;
+ border-radius: 4px;
+ border: 1px solid #1d67b3;
+ margin-left: 12px;
+`;
+
+export const RoleButton = styled.div`
+ background: linear-gradient(#fff, #dedede);
+ color: #444;
+ padding: 5px 10px;
+ font-weight: 700;
+ border-radius: 4px;
+ border: 1px solid #b8b8b8;
+ width: 100px;
+ text-align: center;
+`;
+
+export const Section = styled.div`
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+`;
+
+export const SectionTitle = styled.div`
+ font-weight: bold;
+ width: 100px;
+ text-align: right;
+ padding-right: 15px;
+`;
+
+export const SectionContent = styled.div`
+ display: flex;
+ align-items: center;
+ width: 300px;
+ position: relative;
+`;
+
+export const SectionDropdown = styled.div`
+ position: relative;
+ width: 300px;
+`;
+
+export const SectionDropdownSelected = styled.div`
+ border: 1px solid #ccc;
+ padding: 5px 10px;
+ border-radius: 4px;
+`;
+
+export const SubHeader = styled.div`
+ font-size: 20px;
+ margin-bottom: 15px;
+ display: flex;
+ border-bottom: 1px solid #ccc;
+ padding-bottom: 5px;
+`;
+
+export const TableTitle = styled.div`
+ background: #e3e3e3;
+ border: 1px solid #cccccc;
+ padding: 13px 10px;
+`;
+
+export const TableSearch = styled.div`
+ background: white;
+ border: 1px solid #cccccc;
+ border-radius: 3px;
+ width: 200px;
+ padding: 3px 10px;
+`;
+
+export const TableHeader = styled.div`
+ background-image: linear-gradient(#eee, #e0e0e0);
+ padding: 10px 45px;
+ font-weight: bold;
+ border: 1px solid #ccc;
+ border-top: none;
+`;
+
+export const TableCheckBox = styled.div`
+ width: 10px;
+ height: 10px;
+ margin-right: 20px;
+ border-radius: 3px;
+`;
+
+export const TableItem = styled.div<{ selected?: boolean }>`
+ display: flex;
+ align-items: center;
+ padding: 10px 15px;
+ border-bottom: 1px solid #cccccc;
+ background: ${p => (p.selected ? '#e6f3ff' : 'white')};
+
+ ${TableCheckBox} {
+ background: ${p => (p.selected ? '#1066bb' : 'white')};
+ border: 1px solid ${p => (p.selected ? '#1066bb' : '#ccc')};
+ }
+`;
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/Browser.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/Browser.tsx
new file mode 100644
index 0000000000000..2ee9c28f3d306
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/Browser.tsx
@@ -0,0 +1,121 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+import { AddIcon, LockIcon } from 'design/SVGIcon';
+
+import {
+ BrowserContainer,
+ BrowserContentContainer,
+ BrowserTab,
+ BrowserTabClose,
+ BrowserTabFavicon,
+ BrowserTabs,
+ BrowserTabTitle,
+ BrowserTitleBarButton,
+ BrowserTitleBarButtons,
+ BrowserTitleBarContainer,
+ BrowserURL,
+ BrowserURLContainer,
+ BrowserURLIcon,
+} from 'teleport/Integrations/Enroll/AwsOidc/browser/BrowserComponents';
+
+import { Stage } from 'teleport/Integrations/Enroll/AwsOidc/stages';
+
+interface BrowserProps {
+ stage: Stage;
+}
+
+export function Browser(props: React.PropsWithChildren) {
+ const tabs = [];
+ if (props.stage >= Stage.CreateNewRole) {
+ tabs.push(
+ = Stage.AssignPolicyToRole
+ }
+ key="create-role"
+ >
+
+
+ {props.stage >= Stage.ListRoles ? 'Roles' : 'Create role'}
+
+
+
+
+
+ );
+ }
+
+ if (
+ props.stage >= Stage.CreatePolicy &&
+ props.stage <= Stage.ClickCreatePolicyButton
+ ) {
+ tabs.push(
+
+
+ Create policy
+
+
+
+
+ );
+ }
+
+ //
+ //
+ // Create policy
+ //
+ //
+ //
+ //
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ console.aws.amazon.com
+
+
+
+
+
+
+
+ IAM Management Console
+
+
+
+
+ {tabs}
+
+
+ {props.children}
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/BrowserComponents.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/BrowserComponents.tsx
new file mode 100644
index 0000000000000..484424df9cd99
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/BrowserComponents.tsx
@@ -0,0 +1,129 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import styled from 'styled-components';
+
+import favicon from './favicon.png';
+
+export const BrowserContainer = styled.div`
+ position: relative;
+ border-radius: 5px;
+ width: 100%;
+ box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.43);
+`;
+
+export const BrowserTitleBarContainer = styled.div`
+ background: #040b1d;
+ height: 60px;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+`;
+
+export const BrowserTitleBarButtons = styled.div`
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 10px;
+ transform: translate(0, -50%);
+`;
+
+export const BrowserTitleBarButton = styled.div`
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ margin-right: 5px;
+`;
+
+export const BrowserContentContainer = styled.div`
+ background: white;
+ color: black;
+ height: var(--content-height, 660px);
+ overflow-y: auto;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+`;
+
+export const BrowserCode = styled.div`
+ font-size: 12px;
+ font-family: Menlo, DejaVu Sans Mono, Consolas, Lucida Console, monospace;
+ line-height: 20px;
+ white-space: pre-wrap;
+`;
+
+export const BrowserURLContainer = styled.div`
+ padding: 4px 10px;
+ width: 300px;
+ text-align: center;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 5px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+export const BrowserURL = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+export const BrowserURLIcon = styled.div`
+ margin-right: 5px;
+ top: 2px;
+ position: relative;
+`;
+
+export const BrowserTabs = styled.div`
+ display: flex;
+ background: #060b1d;
+ height: 36px;
+`;
+
+export const BrowserTabFavicon = styled.div`
+ margin-right: 10px;
+ width: 16px;
+ height: 16px;
+ background: url(${favicon}) no-repeat;
+`;
+
+export const BrowserTabClose = styled.div`
+ margin-left: 10px;
+ svg {
+ position: relative;
+ top: 2px;
+ transform: rotate(45deg);
+ }
+`;
+
+export const BrowserTab = styled.div<{ active: boolean }>`
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ background: ${p => (p.active ? 'rgba(255, 255, 255, 0.1)' : 'none')};
+ border-top-left-radius: ${p => (p.active ? '7px' : '0')};
+ border-top-right-radius: ${p => (p.active ? '7px' : '0')};
+ margin-right: 10px;
+
+ ${BrowserTabClose} {
+ display: ${p => (p.active ? 'block' : 'none')};
+ }
+`;
+export const BrowserTabTitle = styled.div`
+ font-size: 13px;
+`;
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/Cursor.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/Cursor.tsx
new file mode 100644
index 0000000000000..e658d67de5d19
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/Cursor.tsx
@@ -0,0 +1,146 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import styled, { keyframes } from 'styled-components';
+
+import { SVGIconProps } from 'design/SVGIcon/common';
+
+const Container = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 10;
+ pointer-events: none;
+`;
+
+const CursorContainer = styled.div`
+ position: absolute;
+ transition: 0.3s linear;
+`;
+
+interface CursorProps {
+ top: number;
+ left: number;
+ click: boolean;
+}
+
+const pulse = keyframes`
+ 0% {
+ transform: scale(0.1, 0.1);
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ transform: scale(1.6, 1.6);
+ opacity: 0;
+ }
+`;
+
+const fadeIn = keyframes`
+ 0% {
+ opacity: 0;
+ }
+
+ 20% {
+ opacity: 1;
+ }
+
+ 80% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0;
+ }
+`;
+
+export const Pulse = styled.div`
+ background: #abc6e4;
+ opacity: 0.5;
+ border-radius: 50%;
+ height: 14px;
+ width: 14px;
+ position: absolute;
+ left: 8px;
+ top: 3px;
+ z-index: 2;
+ animation: ${fadeIn} 2s ease-in forwards;
+
+ &:after {
+ content: '';
+ border-radius: 50%;
+ height: 40px;
+ width: 40px;
+ opacity: 0;
+ box-shadow: 0 0 1px 2px #abc6e4;
+ position: absolute;
+ margin: -13px 0 0 -13px;
+ animation: ${pulse} 2s ease-out infinite;
+ }
+`;
+
+export function Cursor(props: CursorProps) {
+ const [showPulse, setShowPulse] = useState(false);
+
+ useEffect(() => {
+ if (!props.click) {
+ return;
+ }
+
+ const id = window.setTimeout(() => setShowPulse(true), 1000);
+ const id2 = window.setTimeout(() => setShowPulse(false), 3000);
+
+ return () => {
+ clearTimeout(id);
+ clearTimeout(id2);
+ };
+ }, [props.top, props.left, props.click]);
+
+ return (
+
+
+
+
+
+
+ {showPulse && props.click && }
+
+
+ );
+}
+
+function CursorIcon({ size = 40, fill = 'white' }: SVGIconProps) {
+ return (
+
+
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/favicon.png b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/favicon.png
new file mode 100644
index 0000000000000..18151df75eb74
Binary files /dev/null and b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/browser/favicon.png differ
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/index.ts b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/index.ts
new file mode 100644
index 0000000000000..fc9f91c3f8273
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/index.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// export as default for use with React.lazy
+export { AwsOidc as default } from './AwsOidc';
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FifthStageInstructions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FifthStageInstructions.tsx
new file mode 100644
index 0000000000000..35966bb6ba8b4
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FifthStageInstructions.tsx
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+import Text from 'design/Text';
+import { ButtonPrimary } from 'design';
+import Box from 'design/Box';
+
+import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy';
+
+import { InstructionsContainer } from './common';
+
+import type { CommonInstructionsProps } from './common';
+
+export function FifthStageInstructions(props: CommonInstructionsProps) {
+ const policy = `{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": "rds:DescribeDBInstances",
+ "Resource": "*"
+ }
+ ]
+}`;
+
+ return (
+
+
+ Select the JSON tab
+
+
+
+ Replace the JSON with the following
+
+
+
+
+ Click Next: Tags and then Next: Review
+
+
+
+ Give the policy a name and then click Create policy
+
+
+
+ props.onNext()}>Next
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FirstStageInstructions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FirstStageInstructions.tsx
new file mode 100644
index 0000000000000..376fbbd1b985d
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FirstStageInstructions.tsx
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+
+import Box from 'design/Box';
+
+import { ButtonPrimary } from 'design';
+
+import { InstructionsContainer } from './common';
+
+import type { CommonInstructionsProps } from './common';
+
+const InstructionBlock = styled.div`
+ margin-bottom: 30px;
+`;
+
+export function FirstStageInstructions(props: CommonInstructionsProps) {
+ return (
+
+
+ To connect Teleport to AWS as an identity provider, go to the{' '}
+ AWS Management Console
+
+
+ Search for IAM , and then click on{' '}
+ Identity providers
+
+
+ After that, click on Add Provider
+
+
+
+ props.onNext()}>Next
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FourthStageInstructions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FourthStageInstructions.tsx
new file mode 100644
index 0000000000000..5846e1f8b2e3d
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/FourthStageInstructions.tsx
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+import Text from 'design/Text';
+import Box from 'design/Box';
+
+import { ButtonPrimary } from 'design';
+
+import { InstructionsContainer } from './common';
+
+import type { CommonInstructionsProps } from './common';
+
+export function FourthStageInstructions(props: CommonInstructionsProps) {
+ return (
+
+
+ Select discover.teleport as the audience for the role
+
+
+
+ Then click on Next: Permissions
+
+
+
+ From the permissions page, click on Create policy
+
+
+
+ props.onNext()}>Next
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/Instructions.story.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/Instructions.story.tsx
new file mode 100644
index 0000000000000..4125dae4d58c1
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/Instructions.story.tsx
@@ -0,0 +1,68 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { MemoryRouter } from 'react-router';
+
+import { FirstStageInstructions } from './FirstStageInstructions';
+import { SecondStageInstructions } from './SecondStageInstructions';
+import { ThirdStageInstructions } from './ThirdStageInstructions';
+import { FourthStageInstructions } from './FourthStageInstructions';
+import { FifthStageInstructions } from './FifthStageInstructions';
+import { SixthStageInstructions } from './SixthStageInstructions';
+import {
+ SeventhStageInstructions,
+ SuccessfullyAddedIntegrationDialog,
+} from './SeventhStageInstructions';
+
+export default {
+ title: 'Teleport/Integrations/Enroll/AwsOidc/Instructions',
+};
+
+export const Step1 = () => ;
+export const Step2 = () => ;
+export const Step3 = () => ;
+export const Step4 = () => ;
+export const Step5 = () => ;
+export const Step6 = () => ;
+export const Step7 = () => (
+
+
+
+);
+
+export const ConfirmDialog = () => (
+
+
+
+);
+
+export const ConfirmDialogFromDiscover = () => (
+
+
+
+);
+
+const props = {
+ onNext: () => null,
+ clusterPublicUri: 'gravitationalwashington.cloud.gravitional.io:4444',
+};
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SecondStageInstructions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SecondStageInstructions.tsx
new file mode 100644
index 0000000000000..324fdfdac500f
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SecondStageInstructions.tsx
@@ -0,0 +1,112 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+
+import Text from 'design/Text';
+import Box from 'design/Box';
+
+import { ButtonPrimary } from 'design';
+
+import FieldInput from 'shared/components/FieldInput';
+import Validation, { Validator } from 'shared/components/Validation';
+
+import { requiredField } from 'shared/components/Validation/rules';
+
+import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy';
+
+import { InstructionsContainer } from './common';
+
+import type { CommonInstructionsProps } from './common';
+
+export function SecondStageInstructions(props: CommonInstructionsProps) {
+ const [thumbprint, setThumbprint] = useState('');
+
+ function handleSubmit(validator: Validator) {
+ if (!validator.validate()) {
+ return;
+ }
+
+ // TODO(lisa): validate thumbprint with the back.
+ // This is a nice to have, so not a blocker.
+ props.onNext();
+ }
+
+ return (
+
+
+ Now select OpenID Connect
+
+
+
+ Copy the following into Provider URL
+
+
+
+
+
+
+
+ Copy the following into Audience
+
+
+
+
+
+
+
+ Then, click Get thumbprint
+
+
+ Paste the thumbprint below for verification
+
+
+ {({ validator }) => (
+ <>
+
+ setThumbprint(e.target.value)}
+ value={thumbprint}
+ placeholder="Paste the thumbprint here"
+ rule={requiredField('Thumbprint is required')}
+ />
+
+
+ handleSubmit(validator)}>
+ Next
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SeventhStageInstructions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SeventhStageInstructions.tsx
new file mode 100644
index 0000000000000..db9c2ec456f0a
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SeventhStageInstructions.tsx
@@ -0,0 +1,175 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import { useLocation } from 'react-router';
+import { Link } from 'react-router-dom';
+import { Danger } from 'design/Alert';
+import { CircleCheck } from 'design/Icon';
+import { ButtonPrimary, ButtonSecondary, Text, Flex, Box } from 'design';
+import Dialog, {
+ DialogHeader,
+ DialogContent,
+ DialogFooter,
+} from 'design/Dialog';
+
+import FieldInput from 'shared/components/FieldInput';
+import Validation, { Validator } from 'shared/components/Validation';
+import useAttempt from 'shared/hooks/useAttemptNext';
+
+import { requiredField } from 'shared/components/Validation/rules';
+
+import {
+ IntegrationKind,
+ integrationService,
+} from 'teleport/services/integrations';
+import cfg from 'teleport/config';
+
+import { InstructionsContainer } from './common';
+
+export function SeventhStageInstructions() {
+ const location = useLocation<{ discoverEventId: string }>();
+
+ const { attempt, setAttempt } = useAttempt('');
+ const [showConfirmBox, setShowConfirmBox] = useState(false);
+ const [roleArn, setRoleArn] = useState('');
+ const [name, setName] = useState('');
+
+ function handleSubmit(validator: Validator) {
+ if (!validator.validate()) {
+ return;
+ }
+
+ setAttempt({ status: 'processing' });
+ integrationService
+ .createIntegration({
+ name,
+ subKind: IntegrationKind.AwsOidc,
+ awsoidc: { roleArn },
+ })
+ .then(() => setShowConfirmBox(true))
+ .catch((err: Error) =>
+ setAttempt({ status: 'failed', statusText: err.message })
+ );
+ }
+
+ return (
+
+ {attempt.status === 'failed' && (
+ {attempt.statusText}
+ )}
+ From the list of roles, select the role you just created
+
+
+ {({ validator }) => (
+ <>
+ Copy the role ARN and paste it below
+
+ setRoleArn(e.target.value)}
+ value={roleArn}
+ placeholder="Role ARN"
+ rule={requiredField('Role ARN is required')}
+ />
+
+ Give this AWS integration a name
+
+ setName(e.target.value)}
+ value={name}
+ placeholder="Integration name"
+ rule={requiredField('Name is required')}
+ />
+
+
+ handleSubmit(validator)}
+ disabled={attempt.status === 'processing'}
+ >
+ Next
+
+
+ >
+ )}
+
+ {showConfirmBox && (
+
+ )}
+
+ );
+}
+
+export function SuccessfullyAddedIntegrationDialog({
+ discoverEventId,
+ integrationName,
+}: {
+ discoverEventId: string;
+ integrationName: string;
+}) {
+ return (
+ ({ maxWidth: '500px', width: '100%' })}
+ disableEscapeKeyDown={true}
+ onClose={close}
+ open={true}
+ >
+
+
+
+
+
+ AWS integration "{integrationName}" successfully added
+
+
+
+ {discoverEventId ? (
+
+ Begin RDS Enrollment
+
+ ) : (
+
+
+ Go to Integration List
+
+
+ Add Another Integration
+
+
+ )}
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SixthStageInstructions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SixthStageInstructions.tsx
new file mode 100644
index 0000000000000..240dca368be3a
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/SixthStageInstructions.tsx
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+import Box from 'design/Box';
+import Text from 'design/Text';
+
+import { ButtonPrimary } from 'design';
+
+import { InstructionsContainer } from './common';
+
+import type { CommonInstructionsProps } from './common';
+
+export function SixthStageInstructions(props: CommonInstructionsProps) {
+ return (
+
+ Close the "Create policy tab"
+
+
+ Refresh the list of policies and select the policy you just created
+
+
+ Search for the policy you just created and select it
+
+
+ Click Next: Tags and then Next: Review
+
+
+
+ Give the role a name and then click Create role
+
+
+
+ props.onNext()}>Next
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/ThirdStageInstructions.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/ThirdStageInstructions.tsx
new file mode 100644
index 0000000000000..d96c76576907c
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/ThirdStageInstructions.tsx
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+import Text from 'design/Text';
+import Box from 'design/Box';
+
+import { ButtonPrimary } from 'design';
+
+import { InstructionsContainer } from './common';
+
+import type { CommonInstructionsProps } from './common';
+
+export function ThirdStageInstructions(props: CommonInstructionsProps) {
+ return (
+
+
+ Now click Add Provider
+
+
+
+ Select the Identity provider that you just created{' '}
+ ({props.clusterPublicUri})
+
+
+
+ Select Assign role and create a new role
+
+
+
+ props.onNext()}>Next
+
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/common.ts b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/common.ts
new file mode 100644
index 0000000000000..007672122aa1d
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/instructions/common.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import styled from 'styled-components';
+
+export interface CommonInstructionsProps {
+ onNext: () => void;
+ clusterPublicUri: string;
+}
+
+export const InstructionsContainer = styled.div`
+ flex: 0 0 600px;
+ padding-right: 100px;
+`;
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/stages.ts b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/stages.ts
new file mode 100644
index 0000000000000..c8e290064b826
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/stages.ts
@@ -0,0 +1,621 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum Stage {
+ Initial,
+ ClickIdentityProviders,
+ IdentityProviders,
+ ClickAddProvider,
+ NewProvider,
+ NewProviderFullScreen,
+ SelectOpenIDConnect,
+ OpenIDConnectSelected,
+ ClickProviderURL,
+ PastedProviderURL,
+ ClickAudience,
+ PastedAudience,
+ ClickGetThumbprint,
+ ThumbprintLoading,
+ ThumbprintResult,
+ SelectThumbprint,
+ ThumbprintSelected,
+ AddProvider,
+ ProviderAdded,
+ SelectProvider,
+ ProviderView,
+ ClickAssignRole,
+ ShowAssignRoleModal,
+ ClickCreateNewRole,
+ CreateNewRole,
+ SelectAudienceDropdown,
+ ShowAudienceDropdown,
+ ClickDiscoverAudience,
+ DiscoverAudienceSelected,
+ ClickNextPermissions,
+ ConfigureRolePermissions,
+ ClickCreatePolicy,
+ CreatePolicy,
+ ClickJSONTab,
+ ShowJSONEditor,
+ SelectJSONContents,
+ JSONContentsSelected,
+ PolicyJSONPasted,
+ PolicyClickNextTags,
+ PolicyTags,
+ PolicyClickNextReview,
+ PolicyReview,
+ ClickPolicyName,
+ PolicyHasName,
+ ClickCreatePolicyButton,
+ AssignPolicyToRole,
+ ClickRefreshButton,
+ PoliciesLoaded,
+ ClickSearchBox,
+ SearchForPolicy,
+ SelectPolicy,
+ PolicySelected,
+ RoleClickNextTags,
+ RoleTags,
+ RoleClickNextReview,
+ RoleReview,
+ ClickRoleName,
+ RoleHasName,
+ ClickCreateRoleButton,
+ ListRoles,
+ ClickRole,
+ ViewRole,
+}
+
+interface StageItem {
+ kind: Stage;
+ cursor: {
+ top: number;
+ left: number;
+ click?: boolean;
+ };
+ duration?: number;
+ end?: boolean;
+}
+
+export const STAGES: StageItem[] = [
+ {
+ kind: Stage.Initial,
+ cursor: {
+ top: 236,
+ left: 200,
+ click: false,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.ClickIdentityProviders,
+ cursor: {
+ top: 401,
+ left: 60,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.IdentityProviders,
+ cursor: {
+ top: 401,
+ left: 200,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.ClickAddProvider,
+ cursor: {
+ top: 221,
+ left: 530,
+ click: true,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.NewProvider,
+ cursor: {
+ top: 221,
+ left: 530,
+ },
+ end: true,
+ },
+ {
+ kind: Stage.NewProviderFullScreen,
+ cursor: {
+ top: 221,
+ left: 530,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.SelectOpenIDConnect,
+ cursor: {
+ top: 281,
+ left: 350,
+ click: true,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.OpenIDConnectSelected,
+ cursor: {
+ top: 281,
+ left: 350,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.ClickProviderURL,
+ cursor: {
+ top: 416,
+ left: 160,
+ click: true,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.PastedProviderURL,
+ cursor: {
+ top: 416,
+ left: 160,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.ClickAudience,
+ cursor: {
+ top: 491,
+ left: 160,
+ click: true,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.PastedAudience,
+ cursor: {
+ top: 491,
+ left: 160,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickGetThumbprint,
+ cursor: {
+ top: 416,
+ left: 400,
+ click: true,
+ },
+ duration: 1000,
+ },
+ {
+ kind: Stage.ThumbprintLoading,
+ cursor: {
+ top: 416,
+ left: 400,
+ },
+ duration: 1000,
+ },
+ {
+ kind: Stage.ThumbprintResult,
+ cursor: {
+ top: 416,
+ left: 400,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.SelectThumbprint,
+ cursor: {
+ top: 516,
+ left: 100,
+ click: true,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.ThumbprintSelected,
+ cursor: {
+ top: 516,
+ left: 100,
+ },
+ end: true,
+ },
+ {
+ kind: Stage.AddProvider,
+ cursor: {
+ top: 666,
+ left: 420,
+ click: true,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.ProviderAdded,
+ cursor: {
+ top: 516,
+ left: 300,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.SelectProvider,
+ cursor: {
+ top: 301,
+ left: 70,
+ click: true,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.ProviderView,
+ cursor: {
+ top: 301,
+ left: 70,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.ClickAssignRole,
+ cursor: {
+ top: 221,
+ left: 535,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ShowAssignRoleModal,
+ cursor: {
+ top: 221,
+ left: 535,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickCreateNewRole,
+ cursor: {
+ top: 446,
+ left: 500,
+ click: true,
+ },
+ end: true,
+ },
+ {
+ kind: Stage.CreateNewRole,
+ cursor: {
+ top: 446,
+ left: 500,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.SelectAudienceDropdown,
+ cursor: {
+ top: 477,
+ left: 300,
+ click: true,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.ShowAudienceDropdown,
+ cursor: {
+ top: 477,
+ left: 300,
+ },
+ duration: 1000,
+ },
+ {
+ kind: Stage.ClickDiscoverAudience,
+ cursor: {
+ top: 512,
+ left: 300,
+ click: true,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.DiscoverAudienceSelected,
+ cursor: {
+ top: 512,
+ left: 300,
+ },
+ duration: 1000,
+ },
+ {
+ kind: Stage.ClickNextPermissions,
+ cursor: {
+ top: 722,
+ left: 500,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ConfigureRolePermissions,
+ cursor: {
+ top: 422,
+ left: 500,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickCreatePolicy,
+ cursor: {
+ top: 256,
+ left: 75,
+ click: true,
+ },
+ end: true,
+ },
+ {
+ kind: Stage.CreatePolicy,
+ cursor: {
+ top: 256,
+ left: 75,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickJSONTab,
+ cursor: {
+ top: 210,
+ left: 180,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ShowJSONEditor,
+ cursor: {
+ top: 210,
+ left: 180,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.SelectJSONContents,
+ cursor: {
+ top: 320,
+ left: 140,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.JSONContentsSelected,
+ cursor: {
+ top: 320,
+ left: 140,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PolicyJSONPasted,
+ cursor: {
+ top: 320,
+ left: 140,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PolicyClickNextTags,
+ cursor: {
+ top: 722,
+ left: 530,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PolicyTags,
+ cursor: {
+ top: 722,
+ left: 530,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PolicyClickNextReview,
+ cursor: {
+ top: 722,
+ left: 530,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PolicyReview,
+ cursor: {
+ top: 722,
+ left: 530,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickPolicyName,
+ cursor: {
+ top: 262,
+ left: 230,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PolicyHasName,
+ cursor: {
+ top: 262,
+ left: 230,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickCreatePolicyButton,
+ cursor: {
+ top: 722,
+ left: 530,
+ click: true,
+ },
+ end: true,
+ },
+ {
+ kind: Stage.AssignPolicyToRole,
+ cursor: {
+ top: 260,
+ left: 550,
+ },
+ duration: 1000,
+ },
+ {
+ kind: Stage.ClickRefreshButton,
+ cursor: {
+ top: 260,
+ left: 550,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PoliciesLoaded,
+ cursor: {
+ top: 260,
+ left: 550,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.ClickSearchBox,
+ cursor: {
+ top: 325,
+ left: 120,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.SearchForPolicy,
+ cursor: {
+ top: 325,
+ left: 120,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.SelectPolicy,
+ cursor: {
+ top: 420,
+ left: 36,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.PolicySelected,
+ cursor: {
+ top: 440,
+ left: 50,
+ },
+ duration: 1500,
+ },
+ {
+ kind: Stage.RoleClickNextTags,
+ cursor: {
+ top: 722,
+ left: 530,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.RoleTags,
+ cursor: {
+ top: 722,
+ left: 530,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.RoleClickNextReview,
+ cursor: {
+ top: 722,
+ left: 530,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.RoleReview,
+ cursor: {
+ top: 722,
+ left: 530,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickRoleName,
+ cursor: {
+ top: 262,
+ left: 230,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.RoleHasName,
+ cursor: {
+ top: 262,
+ left: 230,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ClickCreateRoleButton,
+ cursor: {
+ top: 722,
+ left: 530,
+ click: true,
+ },
+ end: true,
+ },
+ {
+ kind: Stage.ListRoles,
+ cursor: {
+ top: 722,
+ left: 530,
+ },
+ duration: 2000,
+ },
+ {
+ kind: Stage.ClickRole,
+ cursor: {
+ top: 142,
+ left: 120,
+ click: true,
+ },
+ duration: 2500,
+ },
+ {
+ kind: Stage.ViewRole,
+ cursor: {
+ top: 182,
+ left: 150,
+ },
+ end: true,
+ },
+];
diff --git a/web/packages/teleport/src/IntegrationEnroll/IntegrationEnroll.story.tsx b/web/packages/teleport/src/Integrations/Enroll/IntegrationEnroll.story.tsx
similarity index 82%
rename from web/packages/teleport/src/IntegrationEnroll/IntegrationEnroll.story.tsx
rename to web/packages/teleport/src/Integrations/Enroll/IntegrationEnroll.story.tsx
index 3400e616deee7..a0eeeb07f3c47 100644
--- a/web/packages/teleport/src/IntegrationEnroll/IntegrationEnroll.story.tsx
+++ b/web/packages/teleport/src/Integrations/Enroll/IntegrationEnroll.story.tsx
@@ -17,14 +17,16 @@
import React from 'react';
import { MemoryRouter } from 'react-router';
+import cfg from 'teleport/config';
+
import { IntegrationEnroll } from './IntegrationEnroll';
export default {
- title: 'Teleport/Integrations',
+ title: 'Teleport/Integrations/Enroll',
};
-export const Enroll = () => (
-
+export const Picker = () => (
+
);
diff --git a/web/packages/teleport/src/IntegrationEnroll/IntegrationEnroll.tsx b/web/packages/teleport/src/Integrations/Enroll/IntegrationEnroll.tsx
similarity index 73%
rename from web/packages/teleport/src/IntegrationEnroll/IntegrationEnroll.tsx
rename to web/packages/teleport/src/Integrations/Enroll/IntegrationEnroll.tsx
index d176a1a30efb8..fd7af30b4f391 100644
--- a/web/packages/teleport/src/IntegrationEnroll/IntegrationEnroll.tsx
+++ b/web/packages/teleport/src/Integrations/Enroll/IntegrationEnroll.tsx
@@ -22,13 +22,30 @@ import {
FeatureHeader,
FeatureHeaderTitle,
} from 'teleport/components/Layout';
+import cfg from 'teleport/config';
+import { Route, Switch } from 'teleport/components/Router';
import { IntegrationTiles } from './IntegrationTiles';
import { NoCodeIntegrationDescription } from './common';
+import { getRoutesToEnrollIntegrations } from './IntegrationRoute';
export function IntegrationEnroll() {
return (
+
+ {getRoutesToEnrollIntegrations()}
+
+
+
+ );
+}
+
+export function IntegrationPicker() {
+ return (
+ <>
Select Integration Type
@@ -36,6 +53,6 @@ export function IntegrationEnroll() {
-
+ >
);
}
diff --git a/web/packages/teleport/src/Integrations/Enroll/IntegrationRoute.tsx b/web/packages/teleport/src/Integrations/Enroll/IntegrationRoute.tsx
new file mode 100644
index 0000000000000..f7d64d12819a1
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/Enroll/IntegrationRoute.tsx
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2023 Gravitational, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { lazy } from 'react';
+
+import cfg from 'teleport/config';
+import { Route } from 'teleport/components/Router';
+import { IntegrationKind } from 'teleport/services/integrations';
+
+const EnrollAwsOidc = lazy(
+ () => import(/* webpackChunkName: "enroll-aws-oidc" */ './AwsOidc')
+);
+
+export function getRoutesToEnrollIntegrations() {
+ return [
+ ,
+ ];
+}
diff --git a/web/packages/teleport/src/IntegrationEnroll/IntegrationTiles.test.tsx b/web/packages/teleport/src/Integrations/Enroll/IntegrationTiles.test.tsx
similarity index 100%
rename from web/packages/teleport/src/IntegrationEnroll/IntegrationTiles.test.tsx
rename to web/packages/teleport/src/Integrations/Enroll/IntegrationTiles.test.tsx
diff --git a/web/packages/teleport/src/IntegrationEnroll/IntegrationTiles.tsx b/web/packages/teleport/src/Integrations/Enroll/IntegrationTiles.tsx
similarity index 89%
rename from web/packages/teleport/src/IntegrationEnroll/IntegrationTiles.tsx
rename to web/packages/teleport/src/Integrations/Enroll/IntegrationTiles.tsx
index 69fde068658dd..c3baeb019c989 100644
--- a/web/packages/teleport/src/IntegrationEnroll/IntegrationTiles.tsx
+++ b/web/packages/teleport/src/Integrations/Enroll/IntegrationTiles.tsx
@@ -21,6 +21,7 @@ import awsIcon from 'design/assets/images/icons/aws.svg';
import cfg from 'teleport/config';
import { ToolTipNoPermBadge } from 'teleport/components/ToolTipNoPermBadge';
+import { IntegrationKind } from 'teleport/services/integrations';
import { IntegrationTile } from './common';
@@ -35,7 +36,11 @@ export function IntegrationTiles({
diff --git a/web/packages/teleport/src/IntegrationEnroll/common.tsx b/web/packages/teleport/src/Integrations/Enroll/common.tsx
similarity index 97%
rename from web/packages/teleport/src/IntegrationEnroll/common.tsx
rename to web/packages/teleport/src/Integrations/Enroll/common.tsx
index 150d244161916..8586cffb693ca 100644
--- a/web/packages/teleport/src/IntegrationEnroll/common.tsx
+++ b/web/packages/teleport/src/Integrations/Enroll/common.tsx
@@ -18,8 +18,6 @@ import React from 'react';
import { Flex, Text, Box } from 'design';
import styled from 'styled-components';
-export type IntegrationTypes = 'aws-oidc';
-
export const IntegrationTile = styled(Flex).attrs({
'data-testid': 'tile',
})`
diff --git a/web/packages/teleport/src/IntegrationEnroll/index.ts b/web/packages/teleport/src/Integrations/Enroll/index.ts
similarity index 92%
rename from web/packages/teleport/src/IntegrationEnroll/index.ts
rename to web/packages/teleport/src/Integrations/Enroll/index.ts
index dc480dcb10b22..b7e869e25174d 100644
--- a/web/packages/teleport/src/IntegrationEnroll/index.ts
+++ b/web/packages/teleport/src/Integrations/Enroll/index.ts
@@ -16,6 +16,7 @@
// export as default for use with React.lazy
export { IntegrationEnroll as default } from './IntegrationEnroll';
-export { IntegrationTiles } from './IntegrationTiles';
+export { IntegrationTiles } from './IntegrationTiles';
+export { getRoutesToEnrollIntegrations } from './IntegrationRoute';
export * from './common';
diff --git a/web/packages/teleport/src/Integrations/IntegrationList.tsx b/web/packages/teleport/src/Integrations/IntegrationList.tsx
index 0e414c1d73b80..70212e761bcff 100644
--- a/web/packages/teleport/src/Integrations/IntegrationList.tsx
+++ b/web/packages/teleport/src/Integrations/IntegrationList.tsx
@@ -29,6 +29,7 @@ import {
getStatusCodeTitle,
Integration,
IntegrationStatusCode,
+ IntegrationKind,
Plugin,
} from 'teleport/services/integrations';
@@ -168,7 +169,7 @@ const IconCell = ({ item }: { item: IntegrationLike }) => {
} else {
// Default is integration.
switch (item.kind) {
- case 'aws-oidc':
+ case IntegrationKind.AwsOidc:
formattedText = 'Amazon Web Services (OIDC)';
icon = ;
break;
diff --git a/web/packages/teleport/src/Integrations/Integrations.tsx b/web/packages/teleport/src/Integrations/Integrations.tsx
index 8f1d05f0307e0..2cc8b8d7c1618 100644
--- a/web/packages/teleport/src/Integrations/Integrations.tsx
+++ b/web/packages/teleport/src/Integrations/Integrations.tsx
@@ -25,7 +25,6 @@ import {
} from 'teleport/components/Layout';
import useTeleport from 'teleport/useTeleport';
import { integrationService } from 'teleport/services/integrations';
-import cfg from 'teleport/config';
import { IntegrationsAddButton } from './IntegrationsAddButton';
import { IntegrationList } from './IntegrationList';
@@ -40,9 +39,7 @@ export function Integrations() {
const canCreateIntegrations = ctx.storeUser.getIntegrationsAccess().create;
useEffect(() => {
- run(() =>
- integrationService.fetchIntegrations(cfg.proxyCluster).then(setItems)
- );
+ run(() => integrationService.fetchIntegrations().then(setItems));
}, []);
return (
diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts
index c823d420431e5..c4939a60aef1c 100644
--- a/web/packages/teleport/src/config.ts
+++ b/web/packages/teleport/src/config.ts
@@ -615,10 +615,12 @@ const cfg = {
});
},
- getIntegrationsUrl(clusterId: string, name?: string) {
+ getIntegrationsUrl(integrationName?: string) {
+ // Currently you can only create integrations at the root cluster.
+ const clusterId = cfg.proxyCluster;
return generateResourcePath(cfg.api.integrationsPath, {
clusterId,
- name,
+ name: integrationName,
});
},
diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx
index 1f86fb3bf61ff..18effb253b246 100644
--- a/web/packages/teleport/src/features.tsx
+++ b/web/packages/teleport/src/features.tsx
@@ -105,7 +105,9 @@ const Integrations = React.lazy(
);
const IntegrationEnroll = React.lazy(
() =>
- import(/* webpackChunkName: "integration-enroll" */ './IntegrationEnroll')
+ import(
+ /* webpackChunkName: "integration-enroll" */ '@gravitational/teleport/src/Integrations/Enroll'
+ )
);
// ****************************
diff --git a/web/packages/teleport/src/mocks/contexts.ts b/web/packages/teleport/src/mocks/contexts.ts
index 0e014e22eca4b..a69a131ccac72 100644
--- a/web/packages/teleport/src/mocks/contexts.ts
+++ b/web/packages/teleport/src/mocks/contexts.ts
@@ -84,7 +84,8 @@ const baseContext = {
lastConnected: '2020-09-26T17:30:23.512876876Z',
status: 'online',
nodeCount: 1,
- publicURL: 'localhost',
+ publicURL:
+ 'some-long-cluster-public-url-name.cloud.teleport.gravitational.io:1234',
authVersion: '4.4.0-dev',
proxyVersion: '4.4.0-dev',
},
diff --git a/web/packages/teleport/src/services/integrations/integrations.ts b/web/packages/teleport/src/services/integrations/integrations.ts
index 9e5e8fc8675d7..5ae480253d070 100644
--- a/web/packages/teleport/src/services/integrations/integrations.ts
+++ b/web/packages/teleport/src/services/integrations/integrations.ts
@@ -17,17 +17,31 @@
import api from 'teleport/services/api';
import cfg from 'teleport/config';
-import { Integration, IntegrationStatusCode } from './types';
+import {
+ Integration,
+ IntegrationCreateRequest,
+ IntegrationStatusCode,
+} from './types';
export const integrationService = {
- fetchIntegration(clusterId: string, name: string): Promise {
- return api
- .get(cfg.getIntegrationsUrl(clusterId, name))
- .then(makeIntegration);
+ fetchIntegration(name: string): Promise {
+ return api.get(cfg.getIntegrationsUrl(name)).then(makeIntegration);
},
- fetchIntegrations(clusterId: string): Promise {
- return api.get(cfg.getIntegrationsUrl(clusterId)).then(makeIntegrations);
+ fetchIntegrations(): Promise {
+ return api.get(cfg.getIntegrationsUrl()).then(makeIntegrations);
+ },
+
+ createIntegration(req: IntegrationCreateRequest): Promise {
+ return api.post(cfg.getIntegrationsUrl(), req);
+ },
+
+ updateIntegration(name: string): Promise {
+ return api.put(cfg.getIntegrationsUrl(name));
+ },
+
+ deleteIntegration(name: string): Promise {
+ return api.delete(cfg.getIntegrationsUrl(name));
},
};
diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts
index 4973ea206f1c7..8fcf64b56865e 100644
--- a/web/packages/teleport/src/services/integrations/types.ts
+++ b/web/packages/teleport/src/services/integrations/types.ts
@@ -40,7 +40,12 @@ export type Integration<
details?: string;
statusCode: IntegrationStatusCode;
};
-export type IntegrationKind = 'aws-oidc';
+// IntegrationKind string values should be in sync
+// with the backend value for defining the integration
+// resource's subKind field.
+export enum IntegrationKind {
+ AwsOidc = 'aws-oidc',
+}
export type IntegrationSpecAwsOidc = {
roleArn: string;
};
@@ -86,3 +91,9 @@ export function getStatusCodeDescription(
export type Plugin = Integration<'plugin', PluginKind, PluginSpec>;
export type PluginSpec = Record; // currently no 'spec' fields exposed to the frontend
export type PluginKind = 'slack';
+
+export type IntegrationCreateRequest = {
+ name: string;
+ subKind: IntegrationKind;
+ awsoidc?: IntegrationSpecAwsOidc;
+};