diff --git a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.story.tsx b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.story.tsx index 3e2e283876bd1..0921355f9468b 100644 --- a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.story.tsx +++ b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.story.tsx @@ -16,6 +16,8 @@ import React from 'react'; import { MemoryRouter } from 'react-router'; +import { initialize, mswLoader } from 'msw-storybook-addon'; +import { rest } from 'msw'; import { Context as TeleportContext, ContextProvider } from 'teleport'; import cfg from 'teleport/config'; @@ -30,6 +32,7 @@ import { import { DiscoverProvider, DiscoverContextState, + DbMeta, } from 'teleport/Discover/useDiscover'; import { IntegrationStatusCode } from 'teleport/services/integrations'; @@ -37,8 +40,11 @@ import { AutoDeploy } from './AutoDeploy'; export default { title: 'Teleport/Discover/Database/Deploy/Auto', + loaders: [mswLoader], }; +initialize(); + export const Init = () => { return ( @@ -47,6 +53,18 @@ export const Init = () => { ); }; +Init.parameters = { + msw: { + handlers: [ + rest.post( + cfg.getListSecurityGroupsUrl('test-integration'), + (req, res, ctx) => + res(ctx.json({ securityGroups: securityGroupsResponse })) + ), + ], + }, +}; + export const InitWithLabels = () => { return ( { ); }; +InitWithLabels.parameters = { + msw: { + handlers: [ + rest.post( + cfg.getListSecurityGroupsUrl('test-integration'), + (req, res, ctx) => + res(ctx.json({ securityGroups: securityGroupsResponse })) + ), + ], + }, +}; + +export const InitSecurityGroupsLoadingFailed = () => { + return ( + + + + ); +}; + +InitSecurityGroupsLoadingFailed.parameters = { + msw: { + handlers: [ + rest.post( + cfg.getListSecurityGroupsUrl('test-integration'), + (req, res, ctx) => + res( + ctx.status(403), + ctx.json({ + message: 'some error when trying to list security groups', + }) + ) + ), + ], + }, +}; + +export const InitSecurityGroupsLoading = () => { + return ( + + + + ); +}; + +InitSecurityGroupsLoading.parameters = { + msw: { + handlers: [ + rest.post( + cfg.getListSecurityGroupsUrl('test-integration'), + (req, res, ctx) => res(ctx.delay('infinite')) + ), + ], + }, +}; + const Provider = props => { const ctx = createTeleportContext(); const discoverCtx: DiscoverContextState = { agentMeta: { resourceName: 'db-name', agentMatcherLabels: [], - db: {} as any, + db: { + aws: { + rds: { + region: 'us-east-1', + vpcId: 'test-vpc', + }, + }, + }, selectedAwsRdsDb: { region: 'us-east-1' } as any, integration: { kind: 'aws-oidc', - name: 'integration/aws-oidc', + name: 'test-integration', resourceType: 'integration', spec: { roleArn: 'arn-123', @@ -80,7 +161,7 @@ const Provider = props => { statusCode: IntegrationStatusCode.Running, }, ...props.agentMeta, - }, + } as DbMeta, currentStep: 0, nextStep: () => null, prevStep: () => null, @@ -131,3 +212,153 @@ function createTeleportContext() { return ctx; } + +const securityGroupsResponse = [ + { + name: 'security-group-1', + id: 'sg-1', + description: 'this is security group 1', + inboundRules: [ + { + ipProtocol: 'tcp', + fromPort: '0', + toPort: '0', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '443', + toPort: '443', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '2000', + toPort: '5000', + cidrs: [ + { cidr: '192.168.1.0/24', description: 'Subnet Mask 255.255.255.0' }, + ], + }, + ], + outboundRules: [ + { + ipProtocol: 'tcp', + fromPort: '0', + toPort: '0', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '22', + toPort: '22', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '2000', + toPort: '5000', + cidrs: [ + { cidr: '10.0.0.0/16', description: 'Subnet Mask 255.255.0.0' }, + ], + }, + ], + }, + { + name: 'security-group-2', + id: 'sg-2', + description: 'this is security group 2', + inboundRules: [ + { + ipProtocol: 'tcp', + fromPort: '0', + toPort: '0', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '443', + toPort: '443', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '2000', + toPort: '5000', + cidrs: [ + { cidr: '192.168.1.0/24', description: 'Subnet Mask 255.255.255.0' }, + ], + }, + ], + outboundRules: [ + { + ipProtocol: 'tcp', + fromPort: '0', + toPort: '0', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '22', + toPort: '22', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '2000', + toPort: '5000', + cidrs: [ + { cidr: '10.0.0.0/16', description: 'Subnet Mask 255.255.0.0' }, + ], + }, + ], + }, + { + name: 'security-group-3', + id: 'sg-3', + description: 'this is security group 3', + inboundRules: [ + { + ipProtocol: 'tcp', + fromPort: '0', + toPort: '0', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '443', + toPort: '443', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '2000', + toPort: '5000', + cidrs: [ + { cidr: '192.168.1.0/24', description: 'Subnet Mask 255.255.255.0' }, + ], + }, + ], + outboundRules: [ + { + ipProtocol: 'tcp', + fromPort: '0', + toPort: '0', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '22', + toPort: '22', + cidrs: [{ cidr: '0.0.0.0/0', description: 'Everything' }], + }, + { + ipProtocol: 'tcp', + fromPort: '2000', + toPort: '5000', + cidrs: [ + { cidr: '10.0.0.0/16', description: 'Subnet Mask 255.255.0.0' }, + ], + }, + ], + }, +]; diff --git a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx index e8b5db377bf78..1345da9e2dcae 100644 --- a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx +++ b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx @@ -55,6 +55,8 @@ import { import { DeployServiceProp } from '../DeployService'; import { hasMatchingLabels, Labels } from '../../common'; +import { SelectSecurityGroups } from './SelectSecurityGroups'; + import type { Database } from 'teleport/services/databases'; export function AutoDeploy({ toggleDeployMethod }: DeployServiceProp) { @@ -68,6 +70,10 @@ export function AutoDeploy({ toggleDeployMethod }: DeployServiceProp) { useState(); const [deployFinished, setDeployFinished] = useState(false); + const [selectedSecurityGroups, setSelectedSecurityGroups] = useState< + string[] + >([]); + const hasDbLabels = agentMeta?.agentMatcherLabels?.length; const dbLabels = hasDbLabels ? agentMeta.agentMatcherLabels : []; const [labels, setLabels] = useState([ @@ -101,6 +107,7 @@ export function AutoDeploy({ toggleDeployMethod }: DeployServiceProp) { subnetIds: dbMeta.selectedAwsRdsDb?.subnets, taskRoleArn, databaseAgentMatcherLabels: labels, + securityGroups: selectedSecurityGroups, }) // The user is still technically in the "processing" // state, because after this call succeeds, we will @@ -170,8 +177,8 @@ export function AutoDeploy({ toggleDeployMethod }: DeployServiceProp) { {/* step two */} - Step 2 - + + Step 2 (Optional) + + + {/* step three */} + + + + + + Step 4 + Deploy the Teleport Database Service. handleDeploy(validator)} disabled={attempt.status === 'processing'} + mt={2} mb={2} > Deploy Teleport Service @@ -212,7 +235,6 @@ export function AutoDeploy({ toggleDeployMethod }: DeployServiceProp) { )} - {/* step three */} {isDeploying && ( >; + dbMeta: DbMeta; + emitErrorEvent(err: string): void; +}) => { + const [sgTableData, setSgTableData] = useState({ + items: [], + nextToken: '', + fetchStatus: 'disabled', + }); + + const { attempt, run } = useAttempt('processing'); + + function onSelectSecurityGroup( + sg: SecurityGroup, + e: React.ChangeEvent + ) { + if (e.target.checked) { + return setSelectedSecurityGroups(currentSelectedGroups => [ + ...currentSelectedGroups, + sg.id, + ]); + } else { + setSelectedSecurityGroups( + selectedSecurityGroups.filter(id => id !== sg.id) + ); + } + } + + async function fetchSecurityGroups() { + const integration = dbMeta.integration; + const selectedDb = dbMeta.selectedAwsRdsDb; + + run(() => + integrationService + .fetchSecurityGroups(integration.name, { + vpcId: selectedDb.vpcId, + region: selectedDb.region, + nextToken: sgTableData.nextToken, + }) + .then(({ securityGroups, nextToken }) => { + setSgTableData({ + nextToken: nextToken, + fetchStatus: nextToken ? '' : 'disabled', + items: [...sgTableData.items, ...securityGroups], + }); + }) + .catch((err: Error) => { + const errMsg = getErrMessage(err); + emitErrorEvent(`fetch security groups error: ${errMsg}`); + throw err; + }) + ); + } + + useEffect(() => { + fetchSecurityGroups(); + }, []); + + return ( + <> + Step 3 (Optional) + Select Security Groups + + Select security groups to assign to the Fargate service that will be + running the database access agent. The security groups you pick must + allow outbound connectivity to this Teleport cluster. If you don't + select any security groups, the default one for the VPC will be used. + + {attempt.status === 'failed' && ( + <> + + + {attempt.statusText} + + + Retry + + + )} + {attempt.status === 'processing' && ( + + + + )} + {attempt.status === 'success' && ( + + + + )} + + ); +}; diff --git a/web/packages/teleport/src/Discover/Database/common.tsx b/web/packages/teleport/src/Discover/Database/common.tsx index 1fe57b2a3a6a8..3067e124ef2fb 100644 --- a/web/packages/teleport/src/Discover/Database/common.tsx +++ b/web/packages/teleport/src/Discover/Database/common.tsx @@ -48,7 +48,7 @@ export const Labels = ({ const hasDbLabels = dbLabels.length > 0; return ( - Optionally Define Matcher Labels + Define Matcher Labels {!hasDbLabels && ( Since no labels were defined for the registered database from the diff --git a/web/packages/teleport/src/Discover/Server/CreateEc2Ice/CreateEc2Ice.tsx b/web/packages/teleport/src/Discover/Server/CreateEc2Ice/CreateEc2Ice.tsx index fd05b3f145b87..736109cf8702f 100644 --- a/web/packages/teleport/src/Discover/Server/CreateEc2Ice/CreateEc2Ice.tsx +++ b/web/packages/teleport/src/Discover/Server/CreateEc2Ice/CreateEc2Ice.tsx @@ -17,6 +17,7 @@ import React, { useState, useEffect } from 'react'; import { Box, Indicator, Text, Flex } from 'design'; +import { Warning } from 'design/Icon'; import { Danger } from 'design/Alert'; import { FetchStatus } from 'design/DataTable/types'; @@ -34,6 +35,7 @@ import { import { NodeMeta, useDiscover } from 'teleport/Discover/useDiscover'; import { ActionButtons, + ButtonBlueText, Header, SecurityGroupPicker, } from 'teleport/Discover/Shared'; @@ -169,7 +171,15 @@ export function CreateEc2Ice() { any security groups, the default one for the VPC will be used. {fetchSecurityGroupsAttempt.status === 'failed' && ( - {fetchSecurityGroupsAttempt.statusText} + <> + + + {fetchSecurityGroupsAttempt.statusText} + + + Retry + + )} {fetchSecurityGroupsAttempt.status === 'processing' && ( diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts index 21215bcb0d605..1a24a5ca51d13 100644 --- a/web/packages/teleport/src/services/integrations/types.ts +++ b/web/packages/teleport/src/services/integrations/types.ts @@ -237,6 +237,7 @@ export type AwsOidcDeployServiceRequest = { subnetIds: string[]; taskRoleArn: string; databaseAgentMatcherLabels: Label[]; + securityGroups?: string[]; }; export type AwsOidcDeployServiceResponse = {