From ce5df8007e5d03c1d06d3b64b51fb567aa4ddea1 Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Fri, 24 May 2024 17:30:13 -0700 Subject: [PATCH 1/6] Define ssm endpoint and related types --- .../src/Discover/SelectResource/types.ts | 10 +++++-- web/packages/teleport/src/config.ts | 20 +++++++++++++ .../src/services/discovery/discovery.ts | 8 +++++ .../teleport/src/services/discovery/types.ts | 30 +++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/web/packages/teleport/src/Discover/SelectResource/types.ts b/web/packages/teleport/src/Discover/SelectResource/types.ts index b5d033d42f372..cce21de26d7f1 100644 --- a/web/packages/teleport/src/Discover/SelectResource/types.ts +++ b/web/packages/teleport/src/Discover/SelectResource/types.ts @@ -24,7 +24,10 @@ import { AuthType } from 'teleport/services/user'; import { ResourceKind } from '../Shared/ResourceKind'; -import type { DiscoverEventResource } from 'teleport/services/userEvent'; +import type { + DiscoverDiscoveryConfigMethod, + DiscoverEventResource, +} from 'teleport/services/userEvent'; import type { ResourceIconName } from 'design/ResourceIcon'; @@ -77,8 +80,11 @@ export enum SamlServiceProviderPreset { export interface ResourceSpec { dbMeta?: { location: DatabaseLocation; engine: DatabaseEngine }; - nodeMeta?: { location: ServerLocation }; appMeta?: { awsConsole?: boolean }; + nodeMeta?: { + location: ServerLocation; + discoveryConfigMethod: DiscoverDiscoveryConfigMethod; + }; kubeMeta?: { location: KubeLocation }; samlMeta?: { preset: SamlServiceProviderPreset }; name: string; diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index 1b5b363df3173..1372fad278507 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -319,6 +319,9 @@ const cfg = { awsConfigureIamAppAccessPath: '/v1/webapi/scripts/integrations/configure/aws-app-access-iam.sh?role=:iamRoleName', + awsConfigureIamEc2AutoDiscoverWithSsmPath: + '/v1/webapi/scripts/integrations/configure/ec2-ssm-iam.sh?role=:iamRoleName&awsRegion=:region&ssmDocument=:ssmDocument', + eksClustersListPath: '/v1/webapi/sites/:clusterId/integrations/aws-oidc/:name/eksclusters', eksEnrollClustersPath: @@ -1089,6 +1092,17 @@ const cfg = { ); }, + getAwsIamConfigureScriptEc2AutoDiscoverWithSsmUrl( + params: UrlAwsConfigureIamEc2AutoDiscoverWithSsmScriptParams + ) { + return ( + cfg.baseUrl + + generatePath(cfg.api.awsConfigureIamEc2AutoDiscoverWithSsmPath, { + ...params, + }) + ); + }, + getAwsConfigureIamScriptListDatabasesUrl( params: UrlAwsConfigureIamScriptParams ) { @@ -1248,6 +1262,12 @@ export interface UrlAwsConfigureIamScriptParams { iamRoleName: string; } +export interface UrlAwsConfigureIamEc2AutoDiscoverWithSsmScriptParams { + region: Regions; + iamRoleName: string; + ssmDocument: string; +} + export interface UrlGcpWorkforceConfigParam { orgId: string; poolName: string; diff --git a/web/packages/teleport/src/services/discovery/discovery.ts b/web/packages/teleport/src/services/discovery/discovery.ts index 3ce7042a353be..38a497080dde6 100644 --- a/web/packages/teleport/src/services/discovery/discovery.ts +++ b/web/packages/teleport/src/services/discovery/discovery.ts @@ -73,5 +73,13 @@ function makeAwsMatchersReq(inputMatchers: AwsMatcher[]) { tags: a.tags || {}, integration: a.integration, kube_app_discovery: !!a.kubeAppDiscovery, + ssm: a.ssm ? { document_name: a.ssm.documentName } : undefined, + install: a.install + ? { + enroll_mode: a.install.enrollMode, + install_teleport: a.install.installTeleport, + join_token: a.install.joinToken, + } + : undefined, })); } diff --git a/web/packages/teleport/src/services/discovery/types.ts b/web/packages/teleport/src/services/discovery/types.ts index b117c8d9d5778..a439bd9935c92 100644 --- a/web/packages/teleport/src/services/discovery/types.ts +++ b/web/packages/teleport/src/services/discovery/types.ts @@ -29,6 +29,12 @@ export type DiscoveryConfig = { type AwsMatcherTypes = 'rds' | 'eks' | 'ec2'; +export enum InstallParamEnrollMode { + 'Unspecified' = 0, + 'Script' = 1, + 'Eice' = 2, +} + // AWSMatcher matches AWS EC2 instances, AWS EKS clusters and AWS Databases export type AwsMatcher = { // types are AWS types to match, "ec2", "eks", "rds", "redshift", "elasticache", @@ -43,6 +49,30 @@ export type AwsMatcher = { integration: string; // kubeAppDiscovery specifies if Kubernetes App Discovery should be enabled for a discovered cluster. kubeAppDiscovery?: boolean; + /** + * InstallParams sets the join method when installing on + * discovered EC2 nodes + */ + install?: { + /** + * EnrollMode indicates the mode used to enroll the node into Teleport. + * Valid values: script, eice. + */ + enrollMode: InstallParamEnrollMode; + /** + * InstallTeleport disables agentless discovery + */ + installTeleport: boolean; + /** + * JoinToken is the token to use when joining the cluster + */ + joinToken: string; + }; + /** + * SSM provides options to use when sending a document command to + * an EC2 node + */ + ssm?: { documentName: string }; }; type Labels = Record; From 67cfa14e8c3fec48914d0b5a269bbb38f0c3f5da Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Fri, 24 May 2024 17:30:42 -0700 Subject: [PATCH 2/6] Define ssm as a selectable resource - Make refreshing optional for aws region selector - Define ssm ec2 flow --- .../src/Discover/SelectResource/resources.tsx | 22 +++++++++- .../teleport/src/Discover/Server/index.tsx | 44 ++++++++++++++++++- .../SelfHostedAutoDiscoverDirections.tsx | 22 ++++++---- .../AwsRegionSelector/AwsRegionSelector.tsx | 36 ++++++++------- 4 files changed, 95 insertions(+), 29 deletions(-) diff --git a/web/packages/teleport/src/Discover/SelectResource/resources.tsx b/web/packages/teleport/src/Discover/SelectResource/resources.tsx index cb6d1449da646..930dbe92b2ff1 100644 --- a/web/packages/teleport/src/Discover/SelectResource/resources.tsx +++ b/web/packages/teleport/src/Discover/SelectResource/resources.tsx @@ -20,7 +20,10 @@ import { Platform } from 'design/platform'; import { assertUnreachable } from 'shared/utils/assertUnreachable'; -import { DiscoverEventResource } from 'teleport/services/userEvent'; +import { + DiscoverDiscoveryConfigMethod, + DiscoverEventResource, +} from 'teleport/services/userEvent'; import { ResourceKind } from '../Shared/ResourceKind'; @@ -87,7 +90,22 @@ export const SERVERS: ResourceSpec[] = [ baseServerKeywords + 'ec2 instance connect endpoint aws amazon eice', icon: 'Aws', event: DiscoverEventResource.Ec2Instance, - nodeMeta: { location: ServerLocation.Aws }, + nodeMeta: { + location: ServerLocation.Aws, + discoveryConfigMethod: DiscoverDiscoveryConfigMethod.AwsEc2Eice, + }, + }, + { + name: 'EC2 Auto Discover with SSM', + kind: ResourceKind.Server, + keywords: + baseServerKeywords + 'ec2 instance aws amazon simple systems manager ssm', + icon: 'Aws', + event: DiscoverEventResource.Ec2Instance, + nodeMeta: { + location: ServerLocation.Aws, + discoveryConfigMethod: DiscoverDiscoveryConfigMethod.AwsEc2Ssm, + }, }, { name: 'Connect My Computer', diff --git a/web/packages/teleport/src/Discover/Server/index.tsx b/web/packages/teleport/src/Discover/Server/index.tsx index 9e18226035d4d..1e449b88a8434 100644 --- a/web/packages/teleport/src/Discover/Server/index.tsx +++ b/web/packages/teleport/src/Discover/Server/index.tsx @@ -23,7 +23,11 @@ import { DownloadScript } from 'teleport/Discover/Server/DownloadScript'; import { SetupAccess } from 'teleport/Discover/Server/SetupAccess'; import { TestConnection } from 'teleport/Discover/Server/TestConnection'; import { AwsAccount, ResourceKind, Finished } from 'teleport/Discover/Shared'; -import { DiscoverEvent } from 'teleport/services/userEvent'; +import { + DiscoverDiscoveryConfigMethod, + DiscoverEvent, +} from 'teleport/services/userEvent'; +import cfg from 'teleport/config'; import { ResourceSpec, ServerLocation } from '../SelectResource'; @@ -31,6 +35,8 @@ import { EnrollEc2Instance } from './EnrollEc2Instance/EnrollEc2Instance'; import { CreateEc2Ice } from './CreateEc2Ice/CreateEc2Ice'; import { ServerWrapper } from './ServerWrapper'; +import { DiscoveryConfigSsm } from './DiscoveryConfigSsm/DiscoveryConfigSsm'; +import { ConfigureDiscoveryService } from './ConfigureDiscoveryService/ConfigureDiscoveryService'; export const ServerResource: ResourceViewConfig = { kind: ResourceKind.Server, @@ -51,7 +57,12 @@ export const ServerResource: ResourceViewConfig = { views(resource) { let configureResourceViews; - if (resource && resource.nodeMeta?.location === ServerLocation.Aws) { + const { nodeMeta } = resource; + if ( + nodeMeta?.location === ServerLocation.Aws && + nodeMeta.discoveryConfigMethod === + DiscoverDiscoveryConfigMethod.AwsEc2Eice + ) { configureResourceViews = [ { title: 'Connect AWS Account', @@ -70,6 +81,35 @@ export const ServerResource: ResourceViewConfig = { manuallyEmitSuccessEvent: true, }, ]; + } else if ( + nodeMeta?.location === ServerLocation.Aws && + nodeMeta.discoveryConfigMethod === DiscoverDiscoveryConfigMethod.AwsEc2Ssm + ) { + configureResourceViews = [ + { + title: 'Connect AWS Account', + component: AwsAccount, + eventName: DiscoverEvent.IntegrationAWSOIDCConnectEvent, + }, + // Self hosted requires user to manually install a discovery service. + // Cloud already has a discovery service running, so this step is not required. + ...(!cfg.isCloud + ? [ + { + title: 'Configure Discovery Service', + component: ConfigureDiscoveryService, + eventName: DiscoverEvent.DeployService, + }, + ] + : []), + { + title: cfg.isCloud + ? 'Configure Auto Discovery Service' + : 'Create Discovery Config', + component: DiscoveryConfigSsm, + eventName: DiscoverEvent.CreateDiscoveryConfig, + }, + ]; } else { configureResourceViews = [ { diff --git a/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections.tsx b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections.tsx index ac21c13d20238..992dae8f82780 100644 --- a/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections.tsx +++ b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections.tsx @@ -38,10 +38,12 @@ export const SelfHostedAutoDiscoverDirections = ({ clusterPublicUrl, discoveryGroupName, setDiscoveryGroupName, + showSubHeader = true, }: { clusterPublicUrl: string; discoveryGroupName: string; setDiscoveryGroupName(n: string): void; + showSubHeader?: boolean; }) => { const yamlContent = `version: v3 teleport: @@ -61,14 +63,18 @@ discovery_service: return ( - - - Auto-enrolling requires you to configure a{' '} - Discovery Service - - - -
+ {showSubHeader && ( + <> + + + Auto-enrolling requires you to configure a{' '} + Discovery Service + + + +
+ + )} Step 1: Create a Join Token diff --git a/web/packages/teleport/src/Discover/Shared/AwsRegionSelector/AwsRegionSelector.tsx b/web/packages/teleport/src/Discover/Shared/AwsRegionSelector/AwsRegionSelector.tsx index 544e66fef4bba..bd4b2394fe1c9 100644 --- a/web/packages/teleport/src/Discover/Shared/AwsRegionSelector/AwsRegionSelector.tsx +++ b/web/packages/teleport/src/Discover/Shared/AwsRegionSelector/AwsRegionSelector.tsx @@ -30,7 +30,7 @@ export function AwsRegionSelector({ clear, }: { onFetch(region: Regions): void; - onRefresh(): void; + onRefresh?(): void; disableSelector: boolean; clear(): void; }) { @@ -58,22 +58,24 @@ export function AwsRegionSelector({ isDisabled={disableSelector} />
- - - + {onRefresh && ( + + + + )} ); From dc7148cd22a6da2e68620fea7706ec30bfa820f8 Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Fri, 24 May 2024 17:31:47 -0700 Subject: [PATCH 3/6] Create a manual configure discovery service view for self hosted --- .../ConfigureDiscoveryService.story.tsx | 115 ++++++++++++++++++ .../ConfigureDiscoveryService.tsx | 65 ++++++++++ 2 files changed, 180 insertions(+) create mode 100644 web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx create mode 100644 web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx diff --git a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx new file mode 100644 index 0000000000000..106f2f41acf0f --- /dev/null +++ b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx @@ -0,0 +1,115 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from 'react'; +import { MemoryRouter } from 'react-router'; + +import { ContextProvider } from 'teleport'; +import cfg from 'teleport/config'; +import { createTeleportContext } from 'teleport/mocks/contexts'; +import { + DiscoverProvider, + DiscoverContextState, +} from 'teleport/Discover/useDiscover'; +import { + IntegrationKind, + IntegrationStatusCode, +} from 'teleport/services/integrations'; +import { ResourceKind } from 'teleport/Discover/Shared'; +import { + DiscoverDiscoveryConfigMethod, + DiscoverEventResource, +} from 'teleport/services/userEvent'; +import { ServerLocation } from 'teleport/Discover/SelectResource'; + +import { ConfigureDiscoveryService as Comp } from './ConfigureDiscoveryService'; + +export default { + title: 'Teleport/Discover/Server', +}; + +export const ConfigureDiscoveryService = () => { + return ; +}; + +const Component = () => { + const ctx = createTeleportContext(); + const discoverCtx: DiscoverContextState = { + agentMeta: { + resourceName: 'aws-console', + agentMatcherLabels: [], + awsRegion: 'ap-south-1', + awsIntegration: { + kind: IntegrationKind.AwsOidc, + name: 'some-oidc-name', + resourceType: 'integration', + spec: { + roleArn: 'arn:aws:iam::123456789012:role/test-role-arn', + issuerS3Bucket: '', + issuerS3Prefix: '', + }, + statusCode: IntegrationStatusCode.Running, + }, + autoDiscovery: { + config: { + name: 'discovery-config-name', + discoveryGroup: 'discovery-group-name', + aws: [], + }, + }, + }, + currentStep: 0, + nextStep: () => null, + prevStep: () => null, + onSelectResource: () => null, + resourceSpec: { + name: '', + kind: ResourceKind.Application, + icon: null, + keywords: '', + event: DiscoverEventResource.Ec2Instance, + nodeMeta: { + location: ServerLocation.Aws, + discoveryConfigMethod: DiscoverDiscoveryConfigMethod.AwsEc2Ssm, + }, + }, + exitFlow: () => null, + viewConfig: null, + indexedViews: [], + setResourceSpec: () => null, + updateAgentMeta: () => null, + emitErrorEvent: () => null, + emitEvent: () => null, + eventState: null, + }; + + cfg.proxyCluster = 'localhost'; + return ( + + + + + + + + ); +}; diff --git a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx new file mode 100644 index 0000000000000..2d7ba3abd438f --- /dev/null +++ b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx @@ -0,0 +1,65 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useState } from 'react'; +import { Box, Text } from 'design'; + +import { useDiscover } from 'teleport/Discover/useDiscover'; +import useTeleport from 'teleport/useTeleport'; + +import { SelfHostedAutoDiscoverDirections } from 'teleport/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections'; +import { DEFAULT_DISCOVERY_GROUP_NON_CLOUD } from 'teleport/services/discovery'; + +import { ActionButtons, Header } from '../../Shared'; + +export function ConfigureDiscoveryService() { + const { nextStep, agentMeta, updateAgentMeta } = useDiscover(); + + const [discoveryGroupName, setDiscoveryGroupName] = useState( + DEFAULT_DISCOVERY_GROUP_NON_CLOUD + ); + + const { storeUser } = useTeleport(); + + function handleNextStep() { + updateAgentMeta({ + ...agentMeta, + autoDiscovery: { + config: { name: '', aws: [], discoveryGroup: discoveryGroupName }, + }, + }); + nextStep(); + } + + return ( + +
Configure Teleport Discovery Service
+ + The Teleport Discovery Service can connect to Amazon EC2 and + automatically discover and enroll EC2 instances. + + + +
+ ); +} From 96a2e06fd8eadf4d375c0b32d02424128316ee2c Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Fri, 24 May 2024 17:34:02 -0700 Subject: [PATCH 4/6] Create a discovery config with ssm view --- .../DiscoveryConfigCreatedDialog.tsx | 57 +++ .../DiscoveryConfigSsm.story.tsx | 187 +++++++++ .../DiscoveryConfigSsm/DiscoveryConfigSsm.tsx | 372 ++++++++++++++++++ 3 files changed, 616 insertions(+) create mode 100644 web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx create mode 100644 web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.story.tsx create mode 100644 web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx diff --git a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx new file mode 100644 index 0000000000000..bedf0ec89b24c --- /dev/null +++ b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx @@ -0,0 +1,57 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from 'react'; +import { Text, Flex, ButtonPrimary, Box } from 'design'; +import * as Icons from 'design/Icon'; +import Dialog, { DialogContent } from 'design/DialogConfirmation'; + +import cfg from 'teleport/config'; + +export function DiscoveryConfigCreatedDialog({ + toNextStep, +}: { + toNextStep: () => void; +}) { + return ( + + + + + + Discovery configuration successfully created. + {cfg.isCloud && ( + + It can take up to 30 minutes for the instances to be discovered + and listed in Teleport. + + )} + + + toNextStep()}> + Next + + + + ); +} diff --git a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.story.tsx b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.story.tsx new file mode 100644 index 0000000000000..bc4a4db243854 --- /dev/null +++ b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.story.tsx @@ -0,0 +1,187 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useEffect } from 'react'; +import { MemoryRouter } from 'react-router'; +import { initialize, mswLoader } from 'msw-storybook-addon'; +import { rest } from 'msw'; +import { Info } from 'design/Alert'; + +import { ContextProvider } from 'teleport'; +import cfg from 'teleport/config'; +import { createTeleportContext } from 'teleport/mocks/contexts'; +import { + DiscoverProvider, + DiscoverContextState, +} from 'teleport/Discover/useDiscover'; +import { + IntegrationKind, + IntegrationStatusCode, +} from 'teleport/services/integrations'; +import { ResourceKind } from 'teleport/Discover/Shared'; +import { + DiscoverDiscoveryConfigMethod, + DiscoverEventResource, +} from 'teleport/services/userEvent'; +import { ServerLocation } from 'teleport/Discover/SelectResource'; + +import { DiscoveryConfigSsm } from './DiscoveryConfigSsm'; + +initialize(); + +const defaultIsCloud = cfg.isCloud; +export default { + title: 'Teleport/Discover/Server/EC2/DiscoveryConfigSsm', + loaders: [mswLoader], + decorators: [ + Story => { + useEffect(() => { + // Clean up + return () => { + cfg.isCloud = defaultIsCloud; + }; + }, []); + return ; + }, + ], +}; + +export const SuccessCloud = () => { + cfg.isCloud = true; + return ; +}; +SuccessCloud.parameters = { + msw: { + handlers: [ + rest.post(cfg.api.joinTokenPath, (req, res, ctx) => + res(ctx.json({ id: 'token-id' })) + ), + rest.post(cfg.api.discoveryConfigPath, (req, res, ctx) => + res(ctx.json({ name: 'discovery-cfg-name' })) + ), + ], + }, +}; + +export const SuccessSelfHosted = () => ; +SuccessSelfHosted.parameters = { + msw: { + handlers: [ + rest.post(cfg.api.joinTokenPath, (req, res, ctx) => + res(ctx.json({ id: 'token-id' })) + ), + rest.post(cfg.api.discoveryConfigPath, (req, res, ctx) => + res(ctx.json({ name: 'discovery-cfg-name' })) + ), + ], + }, +}; + +export const Loading = () => { + cfg.isCloud = true; + return ; +}; +Loading.parameters = { + msw: { + handlers: [ + rest.post(cfg.api.joinTokenPath, (req, res, ctx) => + res(ctx.json({ id: 'token-id' })) + ), + rest.post(cfg.api.discoveryConfigPath, (req, res, ctx) => + res(ctx.delay('infinite')) + ), + ], + }, +}; + +export const Failed = () => ; +Failed.parameters = { + msw: { + handlers: [ + rest.post(cfg.api.joinTokenPath, (req, res, ctx) => + res(ctx.json({ id: 'token-id' })) + ), + rest.post(cfg.api.discoveryConfigPath, (req, res, ctx) => + res( + ctx.status(403), + ctx.json({ message: 'Some kind of error message' }) + ) + ), + ], + }, +}; + +const Component = () => { + const ctx = createTeleportContext(); + const discoverCtx: DiscoverContextState = { + agentMeta: { + resourceName: 'aws-console', + agentMatcherLabels: [], + awsIntegration: { + kind: IntegrationKind.AwsOidc, + name: 'some-oidc-name', + resourceType: 'integration', + spec: { + roleArn: 'arn:aws:iam::123456789012:role/test-role-arn', + issuerS3Bucket: '', + issuerS3Prefix: '', + }, + statusCode: IntegrationStatusCode.Running, + }, + }, + currentStep: 0, + nextStep: () => null, + prevStep: () => null, + onSelectResource: () => null, + resourceSpec: { + name: '', + kind: ResourceKind.Application, + icon: null, + keywords: '', + event: DiscoverEventResource.Ec2Instance, + nodeMeta: { + location: ServerLocation.Aws, + discoveryConfigMethod: DiscoverDiscoveryConfigMethod.AwsEc2Ssm, + }, + }, + exitFlow: () => null, + viewConfig: null, + indexedViews: [], + setResourceSpec: () => null, + updateAgentMeta: () => null, + emitErrorEvent: () => null, + emitEvent: () => null, + eventState: null, + }; + + cfg.proxyCluster = 'localhost'; + return ( + + + + Devs: Click next to see next state + + + + + ); +}; diff --git a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx new file mode 100644 index 0000000000000..32c34ddefa240 --- /dev/null +++ b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx @@ -0,0 +1,372 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useState } from 'react'; +import { + Box, + Link as ExternalLink, + Text, + Flex, + ButtonSecondary, + Link, +} from 'design'; +import styled from 'styled-components'; +import { Danger, Info } from 'design/Alert'; +import TextEditor from 'shared/components/TextEditor'; +import { ToolTipInfo } from 'shared/components/ToolTip'; +import FieldInput from 'shared/components/FieldInput'; +import { Rule } from 'shared/components/Validation/rules'; +import Validation, { Validator } from 'shared/components/Validation'; +import { makeEmptyAttempt, useAsync } from 'shared/hooks/useAsync'; +import { TextSelectCopyMulti } from 'shared/components/TextSelectCopy'; + +import cfg from 'teleport/config'; +import { useDiscover } from 'teleport/Discover/useDiscover'; +import { Regions } from 'teleport/services/integrations'; +import { AwsRegionSelector } from 'teleport/Discover/Shared/AwsRegionSelector'; +import JoinTokenService, { JoinToken } from 'teleport/services/joinToken'; + +import { + DISCOVERY_GROUP_CLOUD, + createDiscoveryConfig, + InstallParamEnrollMode, +} from 'teleport/services/discovery'; +import { splitAwsIamArn } from 'teleport/services/integrations/aws'; +import useStickyClusterId from 'teleport/useStickyClusterId'; + +import { ActionButtons, Header, Mark, StyledBox } from '../../Shared'; + +import { DiscoveryConfigCreatedDialog } from './DiscoveryConfigCreatedDialog'; + +const IAM_POLICY_NAME = 'EC2DiscoverWithSSM'; + +export function DiscoveryConfigSsm() { + const { agentMeta, emitErrorEvent, nextStep, updateAgentMeta } = + useDiscover(); + + const { arnResourceName, awsAccountId } = splitAwsIamArn( + agentMeta.awsIntegration.spec.roleArn + ); + + const { clusterId } = useStickyClusterId(); + + const [selectedRegion, setSelectedRegion] = useState(); + const [ssmDocumentName, setSsmDocumentName] = useState( + 'TeleportDiscoveryInstaller' + ); + const [scriptUrl, setScriptUrl] = useState(''); + const [createdToken, setCreatedToken] = useState(); + const [showRestOfSteps, setShowRestOfSteps] = useState(false); + + const [attempt, createJoinTokenAndDiscoveryConfig, setAttempt] = useAsync( + async () => { + try { + const joinTokenService = new JoinTokenService(); + // Don't create another token if token was already created. + // This can happen if creating discovery config attempt failed + // and the user retries. + let joinToken = createdToken; + if (!joinToken) { + joinToken = await joinTokenService.fetchJoinToken({ + roles: ['Node'], + method: 'iam', + rules: [{ awsAccountId }], + }); + setCreatedToken(joinToken); + } + + const config = await createDiscoveryConfig(clusterId, { + name: crypto.randomUUID(), + discoveryGroup: cfg.isCloud + ? DISCOVERY_GROUP_CLOUD + : agentMeta.autoDiscovery.config.discoveryGroup, + aws: [ + { + types: ['ec2'], + regions: [selectedRegion], + tags: { '*': ['*'] }, + integration: agentMeta.awsIntegration.name, + ssm: { documentName: ssmDocumentName }, + install: { + enrollMode: InstallParamEnrollMode.Script, + installTeleport: true, + joinToken: joinToken.id, + }, + }, + ], + }); + + updateAgentMeta({ + ...agentMeta, + awsRegion: selectedRegion, + autoDiscovery: { + config, + }, + }); + } catch (err) { + emitErrorEvent(err.message); + throw err; + } + } + ); + + function generateScriptUrl(validator: Validator) { + if (!validator.validate()) return; + + const scriptUrl = cfg.getAwsIamConfigureScriptEc2AutoDiscoverWithSsmUrl({ + iamRoleName: arnResourceName, + region: selectedRegion, + ssmDocument: ssmDocumentName, + }); + setScriptUrl(scriptUrl); + } + + function clear() { + setAttempt(makeEmptyAttempt); + setCreatedToken(null); + } + + return ( + +
Setup Discovery Config for Teleport Discovery Service
+ {cfg.isCloud ? ( + + The Teleport Discovery Service can connect to Amazon EC2 and + automatically discover and enroll EC2 instances. + + ) : ( + + Discovery config defines the setup that enables Teleport to + automatically discover and register instances. + + )} + {attempt.status === 'error' && ( + {attempt.statusText} + )} + + Step 1 + + + Select the AWS Region that contains the EC2 instances that you would + like to enroll: + + setSelectedRegion(region)} + clear={clear} + disableSelector={!!scriptUrl} + /> + + {!showRestOfSteps && ( + setShowRestOfSteps(true)} + disabled={!selectedRegion} + mt={3} + > + Next + + )} + {scriptUrl && ( + setScriptUrl('')} mt={3}> + Edit + + )} + + {showRestOfSteps && ( + <> + + Step 2 + + Attach AWS managed{' '} + + AmazonSSMManagedInstanceCore + {' '} + policy to EC2 instances IAM profile. The policy enables EC2 + instances to use SSM core functionality. + + + + Step 3 + Each EC2 instance requires{' '} + + SSM Agent + {' '} + to be running. The SSM{' '} + + Nodes Manager dashboard + {' '} + will list all instances that have SSM agent already running. Ensure + ping statuses are Online. + + If you do not see your instances listed in the dashboard, it might + take up to 30 minutes for your instances to use the IAM + credentials you updated in step 2. + + + + {({ validator }) => ( + + Step 4 + + + Give a name for the{' '} + + AWS SSM Document + {' '} + that will be created on your behalf. Required to run the + installer script on each discovered instances. + + setSsmDocumentName(e.target.value)} + placeholder="ssm-document-name" + disabled={!!scriptUrl} + /> + + + scriptUrl ? setScriptUrl('') : generateScriptUrl(validator) + } + disabled={!selectedRegion} + > + {scriptUrl ? 'Edit' : 'Next'} + + + )} + + {scriptUrl && ( + + Step 5 + + + Run the command below on your{' '} + + AWS CloudShell + {' '} + to configure your IAM permissions. + + + The following IAM permissions will be added as an inline + policy named {IAM_POLICY_NAME} to IAM role{' '} + {arnResourceName} + + + + + + + + + + )} + + )} + + {attempt.status === 'success' && ( + + )} + + +
+ ); +} + +const EditorWrapper = styled(Flex)` + flex-directions: column; + height: ${p => p.$height}px; + margin-top: ${p => p.theme.space[3]}px; + width: 450px; +`; + +const inlinePolicyJson = `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ssm:DescribeInstanceInformation", + "ssm:GetCommandInvocation", + "ssm:ListCommandInvocations", + "ssm:SendCommand" + ], + "Resource": "*" + } + ] +}`; + +const SSM_DOCUMENT_NAME_REGEX = new RegExp('^[0-9A-Za-z._-]*$'); +const requiredSsmDocument: Rule = name => () => { + if (!name || name.length < 3 || name.length > 128) { + return { + valid: false, + message: 'name must be between 3 and 128 characters', + }; + } + + const match = name.match(SSM_DOCUMENT_NAME_REGEX); + if (!match) { + return { + valid: false, + message: 'valid characters are a-z, A-Z, 0-9, and _, -, and . only', + }; + } + + return { + valid: true, + }; +}; + +const SharedText = () => ( + <> + The service will execute an install script on these discovered instances + using{' '} + + AWS Systems Manager + {' '} + that will install Teleport, start it and join the cluster. + +); From ee8204f2d518a7d1594a2eafb5038db7b7150304 Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Wed, 29 May 2024 18:31:08 -0700 Subject: [PATCH 5/6] Address design review: remove ec2 instance eice flow --- .../src/Discover/SelectResource/resources.tsx | 17 ++----- .../ConfigureDiscoveryService.tsx | 2 + .../DiscoveryConfigCreatedDialog.tsx | 8 --- .../DiscoveryConfigSsm/DiscoveryConfigSsm.tsx | 3 ++ .../teleport/src/Discover/Server/Shared.tsx | 49 +++++++++++++++++++ .../teleport/src/Discover/Shared/InfoIcon.tsx | 29 +++++++++++ 6 files changed, 86 insertions(+), 22 deletions(-) create mode 100644 web/packages/teleport/src/Discover/Server/Shared.tsx create mode 100644 web/packages/teleport/src/Discover/Shared/InfoIcon.tsx diff --git a/web/packages/teleport/src/Discover/SelectResource/resources.tsx b/web/packages/teleport/src/Discover/SelectResource/resources.tsx index 930dbe92b2ff1..d5ebe43810ede 100644 --- a/web/packages/teleport/src/Discover/SelectResource/resources.tsx +++ b/web/packages/teleport/src/Discover/SelectResource/resources.tsx @@ -84,22 +84,11 @@ export const SERVERS: ResourceSpec[] = [ platform: Platform.macOS, }, { - name: 'EC2 Instance', + name: 'EC2 Auto Enrollment', kind: ResourceKind.Server, keywords: - baseServerKeywords + 'ec2 instance connect endpoint aws amazon eice', - icon: 'Aws', - event: DiscoverEventResource.Ec2Instance, - nodeMeta: { - location: ServerLocation.Aws, - discoveryConfigMethod: DiscoverDiscoveryConfigMethod.AwsEc2Eice, - }, - }, - { - name: 'EC2 Auto Discover with SSM', - kind: ResourceKind.Server, - keywords: - baseServerKeywords + 'ec2 instance aws amazon simple systems manager ssm', + baseServerKeywords + + 'ec2 instance aws amazon simple systems manager ssm auto enrollment', icon: 'Aws', event: DiscoverEventResource.Ec2Instance, nodeMeta: { diff --git a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx index 2d7ba3abd438f..7b5ed2b93e344 100644 --- a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx +++ b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx @@ -26,6 +26,7 @@ import { SelfHostedAutoDiscoverDirections } from 'teleport/Discover/Shared/AutoD import { DEFAULT_DISCOVERY_GROUP_NON_CLOUD } from 'teleport/services/discovery'; import { ActionButtons, Header } from '../../Shared'; +import { SingleEc2InstanceInstallation } from '../Shared'; export function ConfigureDiscoveryService() { const { nextStep, agentMeta, updateAgentMeta } = useDiscover(); @@ -53,6 +54,7 @@ export function ConfigureDiscoveryService() { The Teleport Discovery Service can connect to Amazon EC2 and automatically discover and enroll EC2 instances. + Discovery configuration successfully created. - {cfg.isCloud && ( - - It can take up to 30 minutes for the instances to be discovered - and listed in Teleport. - - )} toNextStep()}> diff --git a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx index 32c34ddefa240..e78512ecbba25 100644 --- a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx +++ b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx @@ -51,6 +51,8 @@ import useStickyClusterId from 'teleport/useStickyClusterId'; import { ActionButtons, Header, Mark, StyledBox } from '../../Shared'; +import { SingleEc2InstanceInstallation } from '../Shared'; + import { DiscoveryConfigCreatedDialog } from './DiscoveryConfigCreatedDialog'; const IAM_POLICY_NAME = 'EC2DiscoverWithSSM'; @@ -155,6 +157,7 @@ export function DiscoveryConfigSsm() { automatically discover and register instances. )} + {cfg.isCloud && } {attempt.status === 'error' && ( {attempt.statusText} )} diff --git a/web/packages/teleport/src/Discover/Server/Shared.tsx b/web/packages/teleport/src/Discover/Server/Shared.tsx new file mode 100644 index 0000000000000..f5b2731797bad --- /dev/null +++ b/web/packages/teleport/src/Discover/Server/Shared.tsx @@ -0,0 +1,49 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from 'react'; +import { Link as InternalLink } from 'react-router-dom'; +import { OutlineInfo } from 'design/Alert/Alert'; +import { Box } from 'design'; + +import cfg from 'teleport/config'; + +import { InfoIcon } from '../Shared/InfoIcon'; +import { Mark } from '../Shared'; + +export const SingleEc2InstanceInstallation = () => ( + + + + + + Auto discovery will enroll all EC2 instances found in a region. If you + want to enroll a single EC2 instance instead, consider + following the{' '} + + Teleport service installation + {' '} + flow instead. + + +); diff --git a/web/packages/teleport/src/Discover/Shared/InfoIcon.tsx b/web/packages/teleport/src/Discover/Shared/InfoIcon.tsx new file mode 100644 index 0000000000000..bdec4998f8f64 --- /dev/null +++ b/web/packages/teleport/src/Discover/Shared/InfoIcon.tsx @@ -0,0 +1,29 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import styled from 'styled-components'; +import { Info } from 'design/Icon'; + +export const InfoIcon = styled(Info)` + background-color: ${p => p.theme.colors.link}; + border-radius: 100px; + height: 32px; + width: 32px; + color: ${p => p.theme.colors.text.primaryInverse}; + margin-right: ${p => p.theme.space[2]}px; +`; From 23d851eee8cdf3d6aba0934707c77601250fa14a Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Wed, 29 May 2024 22:20:43 -0700 Subject: [PATCH 6/6] Address CR --- .../SelectResource.story.test.tsx.snap | 8 +- .../ConfigureDiscoveryService.story.tsx | 2 +- .../ConfigureDiscoveryService.tsx | 4 +- .../DiscoveryConfigCreatedDialog.tsx | 4 + .../DiscoveryConfigSsm/DiscoveryConfigSsm.tsx | 121 +++++++++--------- .../teleport/src/Discover/Server/Shared.tsx | 2 +- .../teleport/src/services/discovery/types.ts | 16 +-- 7 files changed, 82 insertions(+), 75 deletions(-) diff --git a/web/packages/teleport/src/Discover/SelectResource/__snapshots__/SelectResource.story.test.tsx.snap b/web/packages/teleport/src/Discover/SelectResource/__snapshots__/SelectResource.story.test.tsx.snap index ff4f0a3b3346e..f4ecf2f4f6c8a 100644 --- a/web/packages/teleport/src/Discover/SelectResource/__snapshots__/SelectResource.story.test.tsx.snap +++ b/web/packages/teleport/src/Discover/SelectResource/__snapshots__/SelectResource.story.test.tsx.snap @@ -437,7 +437,7 @@ exports[`render with URL loc state set to "server" 1`] = `
- EC2 Instance + EC2 Auto Enrollment
@@ -1256,7 +1256,7 @@ exports[`render with all access 1`] = `
- EC2 Instance + EC2 Auto Enrollment
@@ -3622,7 +3622,7 @@ exports[`render with no access 1`] = `
- EC2 Instance + EC2 Auto Enrollment
@@ -5350,7 +5350,7 @@ exports[`render with partial access 1`] = `
- EC2 Instance + EC2 Auto Enrollment
diff --git a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx index 106f2f41acf0f..1e22bfabee8d0 100644 --- a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx +++ b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.story.tsx @@ -40,7 +40,7 @@ import { ServerLocation } from 'teleport/Discover/SelectResource'; import { ConfigureDiscoveryService as Comp } from './ConfigureDiscoveryService'; export default { - title: 'Teleport/Discover/Server', + title: 'Teleport/Discover/Server/EC2', }; export const ConfigureDiscoveryService = () => { diff --git a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx index 7b5ed2b93e344..1511701659bc2 100644 --- a/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx +++ b/web/packages/teleport/src/Discover/Server/ConfigureDiscoveryService/ConfigureDiscoveryService.tsx @@ -29,7 +29,7 @@ import { ActionButtons, Header } from '../../Shared'; import { SingleEc2InstanceInstallation } from '../Shared'; export function ConfigureDiscoveryService() { - const { nextStep, agentMeta, updateAgentMeta } = useDiscover(); + const { nextStep, prevStep, agentMeta, updateAgentMeta } = useDiscover(); const [discoveryGroupName, setDiscoveryGroupName] = useState( DEFAULT_DISCOVERY_GROUP_NON_CLOUD @@ -61,7 +61,7 @@ export function ConfigureDiscoveryService() { discoveryGroupName={discoveryGroupName} setDiscoveryGroupName={setDiscoveryGroupName} /> - + ); } diff --git a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx index bd3535123897b..4146162067ad5 100644 --- a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx +++ b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigCreatedDialog.tsx @@ -38,6 +38,10 @@ export function DiscoveryConfigCreatedDialog({ Discovery configuration successfully created. + + The discovery service can take a few minutes to finish + auto-enrolling resources. + toNextStep()}> diff --git a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx index e78512ecbba25..799846063ec9c 100644 --- a/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx +++ b/web/packages/teleport/src/Discover/Server/DiscoveryConfigSsm/DiscoveryConfigSsm.tsx @@ -16,15 +16,8 @@ * along with this program. If not, see . */ -import React, { useState } from 'react'; -import { - Box, - Link as ExternalLink, - Text, - Flex, - ButtonSecondary, - Link, -} from 'design'; +import React, { useState, useRef } from 'react'; +import { Box, Link as ExternalLink, Text, Flex, ButtonSecondary } from 'design'; import styled from 'styled-components'; import { Danger, Info } from 'design/Alert'; import TextEditor from 'shared/components/TextEditor'; @@ -58,7 +51,7 @@ import { DiscoveryConfigCreatedDialog } from './DiscoveryConfigCreatedDialog'; const IAM_POLICY_NAME = 'EC2DiscoverWithSSM'; export function DiscoveryConfigSsm() { - const { agentMeta, emitErrorEvent, nextStep, updateAgentMeta } = + const { agentMeta, emitErrorEvent, nextStep, updateAgentMeta, prevStep } = useDiscover(); const { arnResourceName, awsAccountId } = splitAwsIamArn( @@ -72,7 +65,7 @@ export function DiscoveryConfigSsm() { 'TeleportDiscoveryInstaller' ); const [scriptUrl, setScriptUrl] = useState(''); - const [createdToken, setCreatedToken] = useState(); + const joinTokenRef = useRef(); const [showRestOfSteps, setShowRestOfSteps] = useState(false); const [attempt, createJoinTokenAndDiscoveryConfig, setAttempt] = useAsync( @@ -82,14 +75,12 @@ export function DiscoveryConfigSsm() { // Don't create another token if token was already created. // This can happen if creating discovery config attempt failed // and the user retries. - let joinToken = createdToken; - if (!joinToken) { - joinToken = await joinTokenService.fetchJoinToken({ + if (!joinTokenRef.current) { + joinTokenRef.current = await joinTokenService.fetchJoinToken({ roles: ['Node'], method: 'iam', rules: [{ awsAccountId }], }); - setCreatedToken(joinToken); } const config = await createDiscoveryConfig(clusterId, { @@ -107,7 +98,7 @@ export function DiscoveryConfigSsm() { install: { enrollMode: InstallParamEnrollMode.Script, installTeleport: true, - joinToken: joinToken.id, + joinToken: joinTokenRef.current.id, }, }, ], @@ -140,7 +131,19 @@ export function DiscoveryConfigSsm() { function clear() { setAttempt(makeEmptyAttempt); - setCreatedToken(null); + joinTokenRef.current = undefined; + } + + function handleOnSubmit( + e: React.MouseEvent, + validator: Validator + ) { + e.preventDefault(); + if (scriptUrl) { + setScriptUrl(''); + return; + } + generateScriptUrl(validator); } return ( @@ -149,12 +152,12 @@ export function DiscoveryConfigSsm() { {cfg.isCloud ? ( The Teleport Discovery Service can connect to Amazon EC2 and - automatically discover and enroll EC2 instances. + automatically discover and enroll EC2 instances. ) : ( Discovery config defines the setup that enables Teleport to - automatically discover and register instances. + automatically discover and register instances. )} {cfg.isCloud && } @@ -195,12 +198,12 @@ export function DiscoveryConfigSsm() { Step 2 Attach AWS managed{' '} - AmazonSSMManagedInstanceCore - {' '} + {' '} policy to EC2 instances IAM profile. The policy enables EC2 instances to use SSM core functionality. @@ -208,19 +211,19 @@ export function DiscoveryConfigSsm() { Step 3 Each EC2 instance requires{' '} - SSM Agent - {' '} + {' '} to be running. The SSM{' '} - Nodes Manager dashboard - {' '} + {' '} will list all instances that have SSM agent already running. Ensure ping statuses are Online. @@ -231,38 +234,39 @@ export function DiscoveryConfigSsm() { {({ validator }) => ( - - Step 4 - - - Give a name for the{' '} - - AWS SSM Document - {' '} - that will be created on your behalf. Required to run the - installer script on each discovered instances. - - setSsmDocumentName(e.target.value)} - placeholder="ssm-document-name" - disabled={!!scriptUrl} - /> - - - scriptUrl ? setScriptUrl('') : generateScriptUrl(validator) - } - disabled={!selectedRegion} - > - {scriptUrl ? 'Edit' : 'Next'} - - +
+ + Step 4 + + + Give a name for the{' '} + + AWS SSM Document + {' '} + that will be created on your behalf. Required to run the + installer script on each discovered instances. + + setSsmDocumentName(e.target.value)} + placeholder="ssm-document-name" + disabled={!!scriptUrl} + /> + + handleOnSubmit(e, validator)} + disabled={!selectedRegion} + > + {scriptUrl ? 'Edit' : 'Next'} + + +
)}
{scriptUrl && ( @@ -308,6 +312,7 @@ export function DiscoveryConfigSsm() { @@ -360,7 +365,7 @@ const requiredSsmDocument: Rule = name => () => { }; }; -const SharedText = () => ( +const SsmInfoHeaderText = () => ( <> The service will execute an install script on these discovered instances using{' '} diff --git a/web/packages/teleport/src/Discover/Server/Shared.tsx b/web/packages/teleport/src/Discover/Server/Shared.tsx index f5b2731797bad..32ddfc2aed77e 100644 --- a/web/packages/teleport/src/Discover/Server/Shared.tsx +++ b/web/packages/teleport/src/Discover/Server/Shared.tsx @@ -43,7 +43,7 @@ export const SingleEc2InstanceInstallation = () => ( > Teleport service installation {' '} - flow instead. + flow. ); diff --git a/web/packages/teleport/src/services/discovery/types.ts b/web/packages/teleport/src/services/discovery/types.ts index a439bd9935c92..70a82daf03a7e 100644 --- a/web/packages/teleport/src/services/discovery/types.ts +++ b/web/packages/teleport/src/services/discovery/types.ts @@ -30,9 +30,8 @@ export type DiscoveryConfig = { type AwsMatcherTypes = 'rds' | 'eks' | 'ec2'; export enum InstallParamEnrollMode { - 'Unspecified' = 0, - 'Script' = 1, - 'Eice' = 2, + Script = 1, + Eice = 2, } // AWSMatcher matches AWS EC2 instances, AWS EKS clusters and AWS Databases @@ -50,26 +49,25 @@ export type AwsMatcher = { // kubeAppDiscovery specifies if Kubernetes App Discovery should be enabled for a discovered cluster. kubeAppDiscovery?: boolean; /** - * InstallParams sets the join method when installing on + * install sets the join method when installing on * discovered EC2 nodes */ install?: { /** - * EnrollMode indicates the mode used to enroll the node into Teleport. - * Valid values: script, eice. + * enrollMode indicates the mode used to enroll the node into Teleport. */ enrollMode: InstallParamEnrollMode; /** - * InstallTeleport disables agentless discovery + * installTeleport disables agentless discovery */ installTeleport: boolean; /** - * JoinToken is the token to use when joining the cluster + * joinToken is the token to use when joining the cluster */ joinToken: string; }; /** - * SSM provides options to use when sending a document command to + * ssm provides options to use when sending a document command to * an EC2 node */ ssm?: { documentName: string };