Skip to content

Commit

Permalink
refactor: export awsoidc integration UI state and components
Browse files Browse the repository at this point in the history
  • Loading branch information
flyinghermit committed Oct 3, 2024
1 parent 4068883 commit 2ccfe2c
Show file tree
Hide file tree
Showing 7 changed files with 490 additions and 160 deletions.
213 changes: 53 additions & 160 deletions web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,114 +16,40 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useEffect, useState } from 'react';
import { Link as InternalRouteLink } from 'react-router-dom';
import { useLocation } from 'react-router';
import styled from 'styled-components';
import { Box, ButtonSecondary, Text, Link, Flex, ButtonPrimary } from 'design';
import { Box, ButtonPrimary, ButtonSecondary, Flex, Link, Text } from 'design';
import * as Icons from 'design/Icon';
import { Link as InternalRouteLink } from 'react-router-dom';
import FieldInput from 'shared/components/FieldInput';
import Validation from 'shared/components/Validation';
import { requiredIamRoleName } from 'shared/components/Validation/rules';
import Validation, { Validator } from 'shared/components/Validation';
import useAttempt from 'shared/hooks/useAttemptNext';
import styled from 'styled-components';

import {
IntegrationEnrollEvent,
IntegrationEnrollEventData,
IntegrationEnrollKind,
userEventService,
} from 'teleport/services/userEvent';
import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy';
import cfg from 'teleport/config';
import { Header } from 'teleport/Discover/Shared';
import {
ShowConfigurationScript,
RoleArnInput,
} from 'teleport/Integrations/shared';
import { AWS_RESOURCE_GROUPS_TAG_EDITOR_LINK } from 'teleport/Discover/Shared/const';
import { DiscoverUrlLocationState } from 'teleport/Discover/useDiscover';
import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy';
import useStickyClusterId from 'teleport/useStickyClusterId';

import {
Integration,
IntegrationKind,
integrationService,
} from 'teleport/services/integrations';
import cfg from 'teleport/config';

import { FinishDialog } from './FinishDialog';
import { useAwsOidcIntegration } from './useAwsOidcIntegration';

export function AwsOidc() {
const [integrationName, setIntegrationName] = useState('');
const [roleArn, setRoleArn] = useState('');
const [roleName, setRoleName] = useState('');
const [scriptUrl, setScriptUrl] = useState('');
const [createdIntegration, setCreatedIntegration] = useState<Integration>();
const { attempt, run } = useAttempt('');

const {
integrationConfig,
setIntegrationConfig,
scriptUrl,
setScriptUrl,
handleOnCreate,
createdIntegration,
attempt,
generateAwsOidcConfigIdpScript,
} = useAwsOidcIntegration();
const { clusterId } = useStickyClusterId();

const location = useLocation<DiscoverUrlLocationState>();

const [eventData] = useState<IntegrationEnrollEventData>({
id: crypto.randomUUID(),
kind: IntegrationEnrollKind.AwsOidc,
});

useEffect(() => {
// If a user came from the discover wizard,
// discover wizard will send of appropriate events.
if (location.state?.discover) {
return;
}

emitEvent(IntegrationEnrollEvent.Started);
// Only send event once on init.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

function handleOnCreate(validator: Validator) {
if (!validator.validate()) {
return;
}

run(() =>
integrationService
.createIntegration({
name: integrationName,
subKind: IntegrationKind.AwsOidc,
awsoidc: {
roleArn,
},
})
.then(res => {
setCreatedIntegration(res);

if (location.state?.discover) {
return;
}
emitEvent(IntegrationEnrollEvent.Complete);
})
);
}

function emitEvent(event: IntegrationEnrollEvent) {
userEventService.captureIntegrationEnrollEvent({
event,
eventData,
});
}

function generateAwsOidcConfigIdpScript(validator: Validator) {
if (!validator.validate()) {
return;
}

validator.reset();

const newScriptUrl = cfg.getAwsOidcConfigureIdpScriptUrl({
integrationName,
roleName,
});

setScriptUrl(newScriptUrl);
}

return (
<Box pt={3}>
<Header>Set up your AWS account</Header>
Expand Down Expand Up @@ -168,7 +94,7 @@ export function AwsOidc() {
`\n` +
`teleport.dev/origin: integration_awsoidc\n` +
`teleport.dev/integration: ` +
integrationName,
integrationConfig.name,
},
]}
/>
Expand All @@ -178,23 +104,33 @@ export function AwsOidc() {
<Validation>
{({ validator }) => (
<>
<Container mb={5}>
<Container mb={5} width={800}>
<Text bold>Step 1</Text>
<Box width="600px">
<FieldInput
autoFocus={true}
value={integrationName}
value={integrationConfig.name}
label="Give this AWS integration a name"
placeholder="Integration Name"
onChange={e => setIntegrationName(e.target.value)}
onChange={e =>
setIntegrationConfig({
...integrationConfig,
name: e.target.value,
})
}
disabled={!!scriptUrl}
/>
<FieldInput
rule={requiredIamRoleName}
value={roleName}
value={integrationConfig.roleName}
placeholder="IAM Role Name"
label="IAM Role Name"
onChange={e => setRoleName(e.target.value)}
onChange={e =>
setIntegrationConfig({
...integrationConfig,
roleName: e.target.value,
})
}
disabled={!!scriptUrl}
/>
</Box>
Expand All @@ -218,54 +154,27 @@ export function AwsOidc() {
</Container>
{scriptUrl && (
<>
<Container mb={5}>
<Container mb={5} width={800}>
<Text bold>Step 2</Text>
<Text mb={2}>
Open{' '}
<Link
href="https://console.aws.amazon.com/cloudshell/home"
target="_blank"
>
AWS CloudShell
</Link>{' '}
and copy and paste the command that configures the
permissions for you:
</Text>
<Box mb={2}>
<TextSelectCopyMulti
lines={[
{
text: `bash -c "$(curl '${scriptUrl}')"`,
},
]}
/>
</Box>
<ShowConfigurationScript scriptUrl={scriptUrl} />
</Container>
<Container mb={5}>
<Container mb={5} width={800}>
<Text bold>Step 3</Text>
After configuring is finished, go to your{' '}
<Link
target="_blank"
href={`https://console.aws.amazon.com/iamv2/home#/roles/details/${roleName}`}
>
IAM Role dashboard
</Link>{' '}
and copy and paste the ARN below.
<FieldInput
mt={2}
rule={requiredRoleArn(roleName)}
value={roleArn}
label="Role ARN (Amazon Resource Name)"
placeholder={`arn:aws:iam::123456789012:role/${roleName}`}
width="430px"
onChange={e => setRoleArn(e.target.value)}
<RoleArnInput
roleName={integrationConfig.roleName}
roleArn={integrationConfig.roleArn}
setRoleArn={(v: string) =>
setIntegrationConfig({
...integrationConfig,
roleArn: v,
})
}
disabled={attempt.status === 'processing'}
toolTipContent={`Unique AWS resource identifier and uses the format: arn:aws:iam::<ACCOUNT_ID>:role/<IAM_ROLE_NAME>`}
/>
</Container>
</>
)}
{attempt.status === 'failed' && (
{attempt.status === 'error' && (
<Flex>
<Icons.Warning mr={2} color="error.main" size="small" />
<Text color="error.main">Error: {attempt.statusText}</Text>
Expand All @@ -275,7 +184,9 @@ export function AwsOidc() {
<ButtonPrimary
onClick={() => handleOnCreate(validator)}
disabled={
!scriptUrl || attempt.status === 'processing' || !roleArn
!scriptUrl ||
attempt.status === 'processing' ||
!integrationConfig.roleArn
}
>
Create Integration
Expand Down Expand Up @@ -303,24 +214,6 @@ const Container = styled(Box)`
padding: ${p => p.theme.space[3]}px;
`;

const requiredRoleArn = (roleName: string) => (roleArn: string) => () => {
const regex = new RegExp(
'^arn:aws.*:iam::\\d{12}:role\\/(' + roleName + ')$'
);

if (regex.test(roleArn)) {
return {
valid: true,
};
}

return {
valid: false,
message:
'invalid role ARN, double check you copied and pasted the correct output',
};
};

const RouteLink = styled(InternalRouteLink)`
color: ${({ theme }) => theme.colors.buttons.link.default};
Expand Down
Loading

0 comments on commit 2ccfe2c

Please sign in to comment.