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 ( + +
Set up your AWS account
+ + + 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} + +
+ Next: Tags +
+ + ); + } + + if ( + props.stage >= Stage.PolicyTags && + props.stage <= Stage.PolicyClickNextReview + ) { + content = ( + <> + + Add tags - optional + + + + Add tag + + +
+ Next: Review +
+ + ); + } + + if (props.stage >= Stage.PolicyReview) { + content = ( + <> + + Review policy + + +
+ Name* + + + + + {props.stage >= Stage.PolicyHasName ? ( + 'SomePolicyName' + ) : ( + <>  + )} + + + +
+ +
+ Create policy +
+ + ); + } + + return ( + + + +
Create policy
+ + {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 ( + + + +
Create role
+ + 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 + + + )} + + +
+ +
+ Next: Permissions +
+
+
+
+ ); +} 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 + + +
+ Next: Tags +
+ + ); + + if (props.stage >= Stage.RoleReview) { + content = ( + <> + + Review + + +
+ Role Name* + + + + + {props.stage >= Stage.RoleHasName ? ( + 'SomeRoleName' + ) : ( + <>  + )} + + + +
+ +
+ Create role +
+ + ); + } + + if ( + props.stage >= Stage.RoleTags && + props.stage <= Stage.RoleClickNextReview + ) { + content = ( + <> + + Add tags - optional + + + + Add tag + + +
+ Next: Review +
+ + ); + } + + 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 + +
+ )} +
+ +
+ Next: Tags +
+ + ); + } + + return ( + + + +
Create role
+ + {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 ( + + + + ); +} 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 ( +
+
Provider URL
+ + + {providerURL} + + + + + {fingerprintResult} + +
Audience
+ + + {audience} + + + + Add provider + +
+ ); +} 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; +};