diff --git a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx index 33ef00675ef84..68f39d10b398a 100644 --- a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx +++ b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.test.tsx @@ -28,7 +28,10 @@ import DatabaseService from 'teleport/services/databases/databases'; import * as discoveryService from 'teleport/services/discovery/discovery'; import { ComponentWrapper } from 'teleport/Discover/Fixtures/databases'; import cfg from 'teleport/config'; -import { DISCOVERY_GROUP_CLOUD } from 'teleport/services/discovery/discovery'; +import { + DISCOVERY_GROUP_CLOUD, + DEFAULT_DISCOVERY_GROUP_NON_CLOUD, +} from 'teleport/services/discovery/discovery'; import { EnrollRdsDatabase } from './EnrollRdsDatabase'; @@ -207,7 +210,7 @@ describe('test EnrollRdsDatabase.tsx', () => { // Second array are the parameters that this api got called with, // we are interested in the second parameter. expect(createDiscoveryConfig.mock.calls[0][1]['discoveryGroup']).toBe( - 'aws-prod' + DEFAULT_DISCOVERY_GROUP_NON_CLOUD ); expect(DatabaseService.prototype.createDatabase).not.toHaveBeenCalled(); diff --git a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx index a03107eaad2db..94e5010b2a695 100644 --- a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx +++ b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/EnrollRdsDatabase.tsx @@ -17,14 +17,13 @@ */ import React, { useState } from 'react'; -import { Box, Flex, Input, Text, Toggle } from 'design'; +import { Box, Text, Toggle } from 'design'; import { FetchStatus } from 'design/DataTable/types'; import { Danger } from 'design/Alert'; import useAttempt, { Attempt } from 'shared/hooks/useAttemptNext'; import { ToolTipInfo } from 'shared/components/ToolTip'; import { getErrMessage } from 'shared/utils/errorType'; -import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy'; import { DbMeta, useDiscover } from 'teleport/Discover/useDiscover'; import { AwsRdsDatabase, @@ -40,19 +39,24 @@ import { isIamPermError } from 'teleport/Discover/Shared/Aws/error'; import cfg from 'teleport/config'; import { DISCOVERY_GROUP_CLOUD, + DEFAULT_DISCOVERY_GROUP_NON_CLOUD, DiscoveryConfig, createDiscoveryConfig, } from 'teleport/services/discovery'; import useTeleport from 'teleport/useTeleport'; -import { Tabs } from 'teleport/components/Tabs'; -import { ActionButtons, Header, Mark, StyledBox } from '../../Shared'; +import { + AutoEnrollDialog, + ActionButtons, + Header, + Mark, + SelfHostedAutoDiscoverDirections, +} from '../../Shared'; import { useCreateDatabase } from '../CreateDatabase/useCreateDatabase'; import { CreateDatabaseDialog } from '../CreateDatabase/CreateDatabaseDialog'; import { DatabaseList } from './RdsDatabaseList'; -import { AutoEnrollDialog } from './AutoEnrollDialog'; type TableData = { items: CheckedAwsRdsDatabase[]; @@ -106,7 +110,7 @@ export function EnrollRdsDatabase() { const [autoDiscoveryCfg, setAutoDiscoveryCfg] = useState(); const [requiredVpcs, setRequiredVpcs] = useState>(); const [discoveryGroupName, setDiscoveryGroupName] = useState(() => - cfg.isCloud ? '' : 'aws-prod' + cfg.isCloud ? '' : DEFAULT_DISCOVERY_GROUP_NON_CLOUD ); function fetchDatabasesWithNewRegion(region: Regions) { @@ -335,7 +339,9 @@ export function EnrollRdsDatabase() { close={() => setAutoDiscoverAttempt({ status: '' })} retry={handleOnProceed} region={tableData.currRegion} - skipDeployment={requiredVpcs && Object.keys(requiredVpcs).length === 0} + notifyAboutDelay={ + requiredVpcs && Object.keys(requiredVpcs).length === 0 + } /> ); } @@ -435,14 +441,6 @@ function getRdsEngineIdentifier(engine: DatabaseEngine): RdsEngineIdentifier { } } -const discoveryGroupToolTip = `Discovery group name is used to group discovered resources into different sets. \ -This parameter is used to prevent Discovery Agents watching different sets of cloud resources from \ -colliding against each other and deleting resources created by another services.`; - -const discoveryServiceToolTip = `The Discovery Service, is responsible for watching your \ -cloud provider and checking if there are any new databases or if there have been any \ -modifications to previously discovered databases.`; - function ToggleSection({ wantAutoDiscover, toggleWantAutoDiscover, @@ -484,147 +482,3 @@ function ToggleSection({ ); } - -const SelfHostedAutoDiscoverDirections = ({ - clusterPublicUrl, - discoveryGroupName, - setDiscoveryGroupName, -}: { - clusterPublicUrl: string; - discoveryGroupName: string; - setDiscoveryGroupName(n: string): void; -}) => { - const yamlContent = `version: v3 -teleport: - join_params: - token_name: "" - method: token - proxy_server: "${clusterPublicUrl}" -auth_service: - enabled: off -proxy_service: - enabled: off -ssh_service: - enabled: off -discovery_service: - enabled: "yes" - discovery_group: "${discoveryGroupName}"`; - - return ( - - - - Auto-enrolling requires you to configure a{' '} - Discovery Service - - - -
- - Step 1: Create a Join Token - - Run the following command against your Teleport Auth Service and save - it in /tmp/token on the host that will run the Discovery - Service. - - - - - - - Step 2: Define a Discovery Group name{' '} - - - - - setDiscoveryGroupName(e.target.value)} - hasError={discoveryGroupName.length == 0} - /> - - - - - Step 3: Create a teleport.yaml file - - - Use this template to create a teleport.yaml on the host - that will run the Discovery Service. - - - - - - Step 4: Start Discovery Service - - - Configure the Discovery Service to start automatically when the host - boots up by creating a systemd service for it. The instructions depend - on how you installed the Discovery Service. - - - - On the host where you will run the Discovery Service, enable - and start Teleport: - - -
- ), - }, - { - title: `TAR Archive`, - content: ( - - - On the host where you will run the Discovery Service, create - a systemd service configuration for Teleport, enable the - Teleport service, and start Teleport: - - - - ), - }, - ]} - /> - - You can check the status of the Discovery Service with{' '} - systemctl status teleport and view its logs with{' '} - journalctl -fu teleport. - - - - ); -}; diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx index 949355c937f9a..aa62b95b9871b 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx @@ -33,6 +33,7 @@ import { CheckedEksCluster } from './EnrollEksCluster'; type Props = { items: CheckedEksCluster[]; + autoDiscovery: boolean; fetchStatus: FetchStatus; fetchNextPage(): void; @@ -42,6 +43,7 @@ type Props = { export const ClustersList = ({ items = [], + autoDiscovery, fetchStatus = '', fetchNextPage, onSelectCluster, @@ -63,7 +65,7 @@ export const ClustersList = ({ isChecked={isChecked} onChange={onSelectCluster} value={item.name} - {...disabledStates(item)} + {...disabledStates(item, autoDiscovery)} /> ); }, @@ -71,13 +73,15 @@ export const ClustersList = ({ { key: 'name', headerText: 'Name', - render: item => {item.name}, + render: item => ( + {item.name} + ), }, { key: 'labels', headerText: 'Labels', render: item => ( - + ), @@ -89,7 +93,7 @@ export const ClustersList = ({ ), }, @@ -117,9 +121,11 @@ function getStatus(item: CheckedEksCluster) { } } -function disabledStates(item: CheckedEksCluster) { +function disabledStates(item: CheckedEksCluster, autoDiscovery: boolean) { const disabled = - getStatus(item) !== ItemStatus.Success || item.kubeServerExists; + getStatus(item) !== ItemStatus.Success || + item.kubeServerExists || + autoDiscovery; let disabledText = `This EKS cluster is already enrolled and is a part of this cluster`; switch (item.status) { @@ -132,11 +138,8 @@ function disabledStates(item: CheckedEksCluster) { case 'deleting': disabledText = 'Not available'; } - - if (item.status === 'failed') { - disabledText = 'Not available, try refreshing the list'; - } else if (item.status === 'deleting') { - disabledText = 'Not available'; + if (autoDiscovery) { + disabledText = 'All eligible EKS clusters will be enrolled automatically'; } return { disabled, disabledText }; diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx index 373a1bda16f41..21a1b1506fe86 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx @@ -101,6 +101,11 @@ const successEnrollmentHandler = rest.post( } ); +const discoveryConfigHandler = rest.post( + cfg.api.discoveryConfigPath, + (req, res, ctx) => res(ctx.json({})) +); + export const ClustersList = () => ; ClustersList.parameters = { @@ -108,6 +113,7 @@ ClustersList.parameters = { handlers: [ tokenHandler, successEnrollmentHandler, + discoveryConfigHandler, rest.post(cfg.getListEKSClustersUrl(integrationName), (req, res, ctx) => { { return res(ctx.json({ clusters: eksClusters })); @@ -135,6 +141,7 @@ ClustersListInCloud.parameters = { handlers: [ tokenHandler, successEnrollmentHandler, + discoveryConfigHandler, rest.post(cfg.getListEKSClustersUrl(integrationName), (req, res, ctx) => { { return res(ctx.json({ clusters: eksClusters })); diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx index 308360c077b8a..d501f93b3dd51 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx @@ -17,7 +17,7 @@ */ import React, { useState, useCallback } from 'react'; -import { Box, ButtonSecondary, ButtonText, Link, Text, Toggle } from 'design'; +import { Box, ButtonPrimary, ButtonText, Link, Text, Toggle } from 'design'; import styled from 'styled-components'; import { FetchStatus } from 'design/DataTable/types'; import { Danger } from 'design/Alert'; @@ -33,6 +33,12 @@ import { AwsEksCluster, } from 'teleport/services/integrations'; +import { + DISCOVERY_GROUP_CLOUD, + DEFAULT_DISCOVERY_GROUP_NON_CLOUD, + DiscoveryConfig, + createDiscoveryConfig, +} from 'teleport/services/discovery'; import { AwsRegionSelector } from 'teleport/Discover/Shared/AwsRegionSelector'; import { ConfigureIamPerms } from 'teleport/Discover/Shared/Aws/ConfigureIamPerms'; import { isIamPermError } from 'teleport/Discover/Shared/Aws/error'; @@ -43,8 +49,14 @@ import { generateCmd } from 'teleport/Discover/Kubernetes/HelmChart/HelmChart'; import { Kube } from 'teleport/services/kube'; import { JoinToken } from 'teleport/services/joinToken'; +import cfg from 'teleport/config'; -import { Header } from '../../Shared'; +import { + ActionButtons, + Header, + SelfHostedAutoDiscoverDirections, + AutoEnrollDialog, +} from '../../Shared'; import { ClustersList } from './EksClustersList'; import ManualHelmDialog from './ManualHelmDialog'; @@ -94,13 +106,21 @@ export function EnrollEksCluster(props: AgentStepProps) { status: 'notStarted', }); const [isAppDiscoveryEnabled, setAppDiscoveryEnabled] = useState(true); + const [isAutoDiscoveryEnabled, setAutoDiscoveryEnabled] = useState(true); const [isAgentWaitingDialogShown, setIsAgentWaitingDialogShown] = useState(false); const [isManualHelmDialogShown, setIsManualHelmDialogShown] = useState(false); const [waitingResourceId, setWaitingResourceId] = useState(''); + const [discoveryGroupName, setDiscoveryGroupName] = useState(() => + cfg.isCloud ? '' : DEFAULT_DISCOVERY_GROUP_NON_CLOUD + ); + const [autoDiscoveryCfg, setAutoDiscoveryCfg] = useState(); + const { attempt: autoDiscoverAttempt, setAttempt: setAutoDiscoverAttempt } = + useAttempt(''); // join token will be set only if user opens ManualHelmDialog, // we delay it to avoid premature admin action MFA confirmation request. const [joinToken, setJoinToken] = useState(null); + const ctx = useTeleport(); function fetchClustersWithNewRegion(region: Regions) { @@ -193,6 +213,52 @@ export function EnrollEksCluster(props: AgentStepProps) { }); } + async function enableAutoDiscovery() { + setAutoDiscoverAttempt({ status: 'processing' }); + + let discoveryConfig = autoDiscoveryCfg; + if (!discoveryConfig) { + try { + discoveryConfig = await createDiscoveryConfig( + ctx.storeUser.getClusterId(), + { + name: crypto.randomUUID(), + discoveryGroup: cfg.isCloud + ? DISCOVERY_GROUP_CLOUD + : discoveryGroupName, + aws: [ + { + types: ['eks'], + regions: [tableData.currRegion], + tags: { '*': ['*'] }, + integration: agentMeta.awsIntegration.name, + kubeAppDiscovery: isAppDiscoveryEnabled, + }, + ], + } + ); + setAutoDiscoveryCfg(discoveryConfig); + } catch (err) { + const message = getErrMessage(err); + setAutoDiscoverAttempt({ + status: 'failed', + statusText: `failed to create discovery config: ${message}`, + }); + + emitErrorEvent(`failed to create discovery config: ${message}`); + } + } + + setAutoDiscoverAttempt({ status: 'success' }); + props.updateAgentMeta({ + ...agentMeta, + autoDiscovery: { + config: discoveryConfig, + }, + awsRegion: tableData.currRegion, + } as EksMeta); + } + async function enroll() { const integrationName = (agentMeta as EksMeta).awsIntegration.name; setEnrollmentState({ status: 'enrolling' }); @@ -245,6 +311,7 @@ export function EnrollEksCluster(props: AgentStepProps) { async function handleOnProceed() { props.updateAgentMeta({ + ...props.agentMeta, kube: confirmedCluster, resourceName: confirmedCluster.name, } as EksMeta); @@ -253,6 +320,19 @@ export function EnrollEksCluster(props: AgentStepProps) { } const hasIamPermError = isIamPermError(fetchClustersAttempt); + const showContent = + !hasIamPermError && + tableData.currRegion && + fetchClustersAttempt.status === 'success'; + + // (Temp) + // Self hosted auto enroll is different from cloud. + // For cloud, we already run the discovery service for customer. + // For on-prem, user has to run their own discovery service. + // We hide the clusters table for on-prem if they are wanting auto discover + // because it takes up so much space to give them instructions. + // Future work will simply provide user a script so we can show the table then. + const showTable = cfg.isCloud || !isAutoDiscoveryEnabled; const closeEnrollmentDialog = () => { setEnrollmentState({ status: 'notStarted' }); @@ -318,12 +398,12 @@ export function EnrollEksCluster(props: AgentStepProps) { clear={clear} disableSelector={fetchClustersAttempt.status === 'processing'} /> - {!hasIamPermError && tableData.currRegion && ( + {showContent && ( <> setAppDiscoveryEnabled(!isAppDiscoveryEnabled)} + onToggle={() => setAppDiscoveryEnabled(isEnabled => !isEnabled)} > Enable Kubernetes App Discovery @@ -334,38 +414,75 @@ export function EnrollEksCluster(props: AgentStepProps) { Kubernetes cluster. - - - - Automatically enroll selected EKS cluster - setAutoDiscoveryEnabled(isEnabled => !isEnabled)} > - Enroll EKS Cluster - - - + Auto-enroll all EKS clusters for selected region + + + Auto-enroll will automatically identify all EKS clusters from + the selected region and register them as Kubernetes resources in + your infrastructure. + + + + {showTable && ( + + )} + {!cfg.isCloud && isAutoDiscoveryEnabled && ( + + )} + {!isAutoDiscoveryEnabled && ( + + Automatically enroll selected EKS cluster + { - setIsManualHelmDialogShown(b => !b); - }} - pl={0} + mt={2} + mb={2} > - Or enroll manually - - - + Enroll EKS Cluster + + + { + setIsManualHelmDialogShown(b => !b); + }} + pl={0} + > + Or enroll manually + + + + )} + {isAutoDiscoveryEnabled && ( + + )} )} {hasIamPermError && ( @@ -416,6 +533,16 @@ export function EnrollEksCluster(props: AgentStepProps) { next={handleOnProceed} /> )} + {autoDiscoverAttempt.status !== '' && ( + setAutoDiscoverAttempt({ status: '' })} + retry={enableAutoDiscovery} + region={tableData.currRegion} + notifyAboutDelay={true} + /> + )} ); } diff --git a/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.story.tsx b/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.story.tsx index 9f6cecfd548c4..f88fc7b688c45 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.story.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.story.tsx @@ -41,6 +41,32 @@ export const WithTraits = () => ( ); +export const WithTraitsAutoDiscovery = () => ( + + + +); + export const NoAccess = () => ( diff --git a/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.tsx b/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.tsx index aff3a76e279f4..4c071b92b28ae 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/SetupAccess/SetupAccess.tsx @@ -17,7 +17,7 @@ */ import React, { useState, useEffect } from 'react'; -import { Box } from 'design'; +import { Box, Text } from 'design'; import { SelectCreatable, @@ -41,6 +41,7 @@ export function SetupAccess(props: State) { initSelectedOptions, getFixedOptions, getSelectableOptions, + agentMeta, ...restOfProps } = props; const [groupInputValue, setGroupInputValue] = useState(''); @@ -49,6 +50,7 @@ export function SetupAccess(props: State) { const [userInputValue, setUserInputValue] = useState(''); const [selectedUsers, setSelectedUsers] = useState([]); + const wantAutoDiscover = !!agentMeta.autoDiscovery; useEffect(() => { if (props.attempt.status === 'success') { setSelectedGroups(initSelectedOptions('kubeGroups')); @@ -85,7 +87,17 @@ export function SetupAccess(props: State) { } function handleOnProceed() { - onProceed({ kubeGroups: selectedGroups, kubeUsers: selectedUsers }); + let numStepsToIncrement; + // Skip test connection since test connection currently + // only supports one resource testing and auto enrolling + // enrolls resources > 1. + if (wantAutoDiscover) { + numStepsToIncrement = 2; + } + onProceed( + { kubeGroups: selectedGroups, kubeUsers: selectedUsers }, + numStepsToIncrement + ); } const hasTraits = selectedGroups.length > 0 || selectedUsers.length > 0; @@ -101,7 +113,15 @@ export function SetupAccess(props: State) { traitDescription="users and groups" hasTraits={hasTraits} onProceed={handleOnProceed} + wantAutoDiscover={wantAutoDiscover} > + {wantAutoDiscover && ( + + Since auto-discovery is enabled, make sure to include all Kubernetes + users and groups that will be used to connect to the discovered + clusters. + + )} Kubernetes Groups Discovery config successfully created. - {skipDeployment && ( + {notifyAboutDelay && ( <> {' '} The discovery service can take a few minutes to finish - auto-enrolling RDS databases found in region{' '} - {region}. + auto-enrolling resources found in region {region}. )} diff --git a/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections.tsx b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections.tsx new file mode 100644 index 0000000000000..ac21c13d20238 --- /dev/null +++ b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoverDirections.tsx @@ -0,0 +1,186 @@ +/** + * 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 { Box, Flex, Input, Text } from 'design'; +import styled from 'styled-components'; + +import { ToolTipInfo } from 'shared/components/ToolTip'; + +import React from 'react'; + +import { Mark } from 'teleport/Discover/Shared'; +import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy'; +import { Tabs } from 'teleport/components/Tabs'; + +const discoveryGroupToolTip = `Discovery group name is used to group discovered resources into different sets. \ +This parameter is used to prevent Discovery Agents watching different sets of cloud resources from \ +colliding against each other and deleting resources created by another services.`; + +const discoveryServiceToolTip = `The Discovery Service is responsible for watching your \ +cloud provider and checking if there are any new resources or if there have been any \ +modifications to previously discovered resources.`; + +export const SelfHostedAutoDiscoverDirections = ({ + clusterPublicUrl, + discoveryGroupName, + setDiscoveryGroupName, +}: { + clusterPublicUrl: string; + discoveryGroupName: string; + setDiscoveryGroupName(n: string): void; +}) => { + const yamlContent = `version: v3 +teleport: + join_params: + token_name: "" + method: token + proxy_server: "${clusterPublicUrl}" +auth_service: + enabled: off +proxy_service: + enabled: off +ssh_service: + enabled: off +discovery_service: + enabled: "yes" + discovery_group: "${discoveryGroupName}"`; + + return ( + + + + Auto-enrolling requires you to configure a{' '} + Discovery Service + + + +
+ + Step 1: Create a Join Token + + Run the following command against your Teleport Auth Service and save + it in /tmp/token on the host that will run the Discovery + Service. + + + + + + + Step 2: Define a Discovery Group name{' '} + + + + + setDiscoveryGroupName(e.target.value)} + hasError={discoveryGroupName.length == 0} + /> + + + + + Step 3: Create a teleport.yaml file + + + Use this template to create a teleport.yaml on the host + that will run the Discovery Service. + + + + + + Step 4: Start Discovery Service + + + Configure the Discovery Service to start automatically when the host + boots up by creating a systemd service for it. The instructions depend + on how you installed the Discovery Service. + + + + On the host where you will run the Discovery Service, enable + and start Teleport: + + +
+ ), + }, + { + title: `TAR Archive`, + content: ( + + + On the host where you will run the Discovery Service, create + a systemd service configuration for Teleport, enable the + Teleport service, and start Teleport: + + + + ), + }, + ]} + /> + + You can check the status of the Discovery Service with{' '} + systemctl status teleport and view its logs with{' '} + journalctl -fu teleport. + + +
+ ); +}; + +const StyledBox = styled(Box)` + max-width: 1000px; + background-color: ${props => props.theme.colors.spotBackground[0]}; + padding: ${props => `${props.theme.space[3]}px`}; + border-radius: ${props => `${props.theme.space[2]}px`}; +`; diff --git a/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoveryDirections.story.tsx b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoveryDirections.story.tsx new file mode 100644 index 0000000000000..81b0e58b25e2c --- /dev/null +++ b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/SelfHostedAutoDiscoveryDirections.story.tsx @@ -0,0 +1,34 @@ +/** + * 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 { SelfHostedAutoDiscoverDirections } from './SelfHostedAutoDiscoverDirections'; + +export default { + title: 'Teleport/Discover/Shared/SelfHostedAutoDiscoveryDirections', +}; + +export const Directions = () => { + return ( + {}} + /> + ); +}; diff --git a/web/packages/teleport/src/Discover/Shared/AutoDiscovery/index.ts b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/index.ts new file mode 100644 index 0000000000000..387da8a33da5d --- /dev/null +++ b/web/packages/teleport/src/Discover/Shared/AutoDiscovery/index.ts @@ -0,0 +1,20 @@ +/** + * 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 . + */ + +export { AutoEnrollDialog } from './AutoEnrollDialog'; +export { SelfHostedAutoDiscoverDirections } from './SelfHostedAutoDiscoverDirections'; diff --git a/web/packages/teleport/src/Discover/Shared/SetupAccess/useUserTraits.ts b/web/packages/teleport/src/Discover/Shared/SetupAccess/useUserTraits.ts index a6e8ce4fea85d..e3473555b0aa5 100644 --- a/web/packages/teleport/src/Discover/Shared/SetupAccess/useUserTraits.ts +++ b/web/packages/teleport/src/Discover/Shared/SetupAccess/useUserTraits.ts @@ -61,15 +61,17 @@ export function useUserTraits() { let staticTraits = initUserTraits(); switch (resourceSpec.kind) { case ResourceKind.Kubernetes: - const kube = (agentMeta as KubeMeta).kube; - staticTraits.kubeUsers = arrayStrDiff( - kube.users, - dynamicTraits.kubeUsers - ); - staticTraits.kubeGroups = arrayStrDiff( - kube.groups, - dynamicTraits.kubeGroups - ); + if (!wantAutoDiscover) { + const kube = (agentMeta as KubeMeta).kube; + staticTraits.kubeUsers = arrayStrDiff( + kube.users, + dynamicTraits.kubeUsers + ); + staticTraits.kubeGroups = arrayStrDiff( + kube.groups, + dynamicTraits.kubeGroups + ); + } break; case ResourceKind.Server: @@ -135,10 +137,13 @@ export function useUserTraits() { } }); - nextStep({ - kubeUsers: [...newDynamicKubeUsers], - kubeGroups: [...newDynamicKubeGroups], - }); + nextStep( + { + kubeUsers: [...newDynamicKubeUsers], + kubeGroups: [...newDynamicKubeGroups], + }, + numStepsToIncrement + ); break; case ResourceKind.Server: diff --git a/web/packages/teleport/src/Discover/Shared/index.ts b/web/packages/teleport/src/Discover/Shared/index.ts index ec76c42daeadd..078560ee4edd7 100644 --- a/web/packages/teleport/src/Discover/Shared/index.ts +++ b/web/packages/teleport/src/Discover/Shared/index.ts @@ -43,6 +43,10 @@ export { RadioCell, StatusCell, } from './Aws'; +export { + AutoEnrollDialog, + SelfHostedAutoDiscoverDirections, +} from './AutoDiscovery'; export { StyledBox } from './StyledBox'; export type { DiscoverLabel } from './LabelsCreater'; diff --git a/web/packages/teleport/src/Discover/useDiscover.tsx b/web/packages/teleport/src/Discover/useDiscover.tsx index fcc0a72ee5b92..755d8516acf01 100644 --- a/web/packages/teleport/src/Discover/useDiscover.tsx +++ b/web/packages/teleport/src/Discover/useDiscover.tsx @@ -491,7 +491,7 @@ type BaseMeta = { // requiredVpcsAndSubnets is a map of required vpcs for auto discovery. // If this is empty, then a user can skip deploying db agents. // If >0, auto discovery requires deploying db agents. - requiredVpcsAndSubnets: Record; + requiredVpcsAndSubnets?: Record; }; }; diff --git a/web/packages/teleport/src/services/discovery/discovery.ts b/web/packages/teleport/src/services/discovery/discovery.ts index d458b7d27387f..3ce7042a353be 100644 --- a/web/packages/teleport/src/services/discovery/discovery.ts +++ b/web/packages/teleport/src/services/discovery/discovery.ts @@ -23,12 +23,18 @@ import { AwsMatcher, DiscoveryConfig } from './types'; // when creating a discovery config. export const DISCOVERY_GROUP_CLOUD = 'cloud-discovery-group'; +export const DEFAULT_DISCOVERY_GROUP_NON_CLOUD = 'aws-prod'; + export function createDiscoveryConfig( clusterId: string, req: DiscoveryConfig ): Promise { return api - .post(cfg.getDiscoveryConfigUrl(clusterId), req) + .post(cfg.getDiscoveryConfigUrl(clusterId), { + name: req.name, + discoveryGroup: req.discoveryGroup, + aws: makeAwsMatchersReq(req.aws), + }) .then(makeDiscoveryConfig); } @@ -42,15 +48,30 @@ export function makeDiscoveryConfig(rawResp: DiscoveryConfig): DiscoveryConfig { }; } -function makeAws(rawResp: AwsMatcher[]) { - if (!rawResp) { +function makeAws(rawAwsMatchers): AwsMatcher[] { + if (!rawAwsMatchers) { + return []; + } + + return rawAwsMatchers.map(a => ({ + types: a.types || [], + regions: a.regions || [], + tags: a.tags || {}, + integration: a.integration, + kubeAppDiscovery: !!a.kube_app_discovery, + })); +} + +function makeAwsMatchersReq(inputMatchers: AwsMatcher[]) { + if (!inputMatchers) { return []; } - return rawResp.map(a => ({ + return inputMatchers.map(a => ({ types: a.types || [], regions: a.regions || [], tags: a.tags || {}, integration: a.integration, + kube_app_discovery: !!a.kubeAppDiscovery, })); } diff --git a/web/packages/teleport/src/services/discovery/types.ts b/web/packages/teleport/src/services/discovery/types.ts index 0329e1c8e48c3..b117c8d9d5778 100644 --- a/web/packages/teleport/src/services/discovery/types.ts +++ b/web/packages/teleport/src/services/discovery/types.ts @@ -27,20 +27,22 @@ export type DiscoveryConfig = { aws: AwsMatcher[]; }; -type AwsMatcherDatabaseTypes = 'ec2' | 'rds'; +type AwsMatcherTypes = 'rds' | 'eks' | 'ec2'; -// AWSMatcher matches AWS EC2 instances and AWS Databases +// AWSMatcher matches AWS EC2 instances, AWS EKS clusters and AWS Databases export type AwsMatcher = { - // types are AWS database types to match, "ec2", "rds", "redshift", "elasticache", + // types are AWS types to match, "ec2", "eks", "rds", "redshift", "elasticache", // or "memorydb". - types: AwsMatcherDatabaseTypes[]; - // regions are AWS regions to query for databases. + types: AwsMatcherTypes[]; + // regions are AWS regions to query for resources. regions: Regions[]; // tags are AWS resource tags to match. tags: Labels; // integration is the integration name used to generate credentials to interact with AWS APIs. // Environment credentials will not be used when this value is set. integration: string; + // kubeAppDiscovery specifies if Kubernetes App Discovery should be enabled for a discovered cluster. + kubeAppDiscovery?: boolean; }; type Labels = Record;