From bc1978259efd79db884c1348743ca5a7ea3dc076 Mon Sep 17 00:00:00 2001 From: Michael Myers Date: Thu, 2 Jan 2025 16:58:05 -0600 Subject: [PATCH] Move DeviceTrust EmptyList to OSS This takes the empty list components from e and moves them to OSS. The main difference is the different CTAs and buttons now displayed in oss v e. The empty list itself is unchanged, only moved --- .../src/DeviceTrust/DeviceList/DeviceList.tsx | 116 +++++ .../src/DeviceTrust/DeviceList/index.ts | 19 + .../src/DeviceTrust/DeviceTrustLocked.tsx | 152 +----- .../teleport/src/DeviceTrust/EmptyList.tsx | 488 ++++++++++++++++++ web/packages/teleport/src/features.tsx | 5 +- 5 files changed, 632 insertions(+), 148 deletions(-) create mode 100644 web/packages/teleport/src/DeviceTrust/DeviceList/DeviceList.tsx create mode 100644 web/packages/teleport/src/DeviceTrust/DeviceList/index.ts create mode 100644 web/packages/teleport/src/DeviceTrust/EmptyList.tsx diff --git a/web/packages/teleport/src/DeviceTrust/DeviceList/DeviceList.tsx b/web/packages/teleport/src/DeviceTrust/DeviceList/DeviceList.tsx new file mode 100644 index 0000000000000..6f28ed9a62599 --- /dev/null +++ b/web/packages/teleport/src/DeviceTrust/DeviceList/DeviceList.tsx @@ -0,0 +1,116 @@ +/** + * 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 Box from 'design/Box'; +import Table, { Cell } from 'design/DataTable'; +import { ResourceIcon, ResourceIconName } from 'design/ResourceIcon'; +import { P2 } from 'design/Text'; + +import { + DeviceListProps, + TrustedDeviceOSType, +} from 'teleport/DeviceTrust/types'; + +export const DeviceList = ({ + items = [], + pageSize = 50, + pagerPosition = null, + fetchStatus = '', + fetchData, +}: DeviceListProps) => { + return ( + , + }, + { + key: 'assetTag', + headerText: 'Asset Tag', + }, + { + key: 'enrollStatus', + headerText: 'Enroll Status', + render: ({ enrollStatus }) => ( + + ), + }, + { + key: 'owner', + headerText: 'Owner', + }, + ]} + emptyText="No Devices Found" + pagination={{ pageSize, pagerPosition }} + fetching={{ onFetchMore: fetchData, fetchStatus }} + isSearchable + /> + ); +}; + +const EnrollmentStatusCell = ({ status }: { status: string }) => { + const enrolled = status === 'enrolled'; + return ( + + + {status} + + ); +}; + +export const IconCell = ({ osType }: { osType: TrustedDeviceOSType }) => { + let iconName: ResourceIconName; + switch (osType) { + case 'Windows': + iconName = 'microsoft'; + break; + case 'Linux': + iconName = 'linux'; + break; + case 'macOS': + iconName = 'apple'; + break; + } + return ( + + + {osType} + + ); +}; + +const EnrollmentIcon = styled(Box)<{ enrolled: boolean }>` + width: 12px; + height: 12px; + margin-right: ${p => p.theme.space[1]}px; + border-radius: 50%; +background-color: ${p => + p.enrolled ? p.theme.colors.success.main : p.theme.colors.error.main}; + }; +`; diff --git a/web/packages/teleport/src/DeviceTrust/DeviceList/index.ts b/web/packages/teleport/src/DeviceTrust/DeviceList/index.ts new file mode 100644 index 0000000000000..3e4796db94dac --- /dev/null +++ b/web/packages/teleport/src/DeviceTrust/DeviceList/index.ts @@ -0,0 +1,19 @@ +/** + * 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 { DeviceList } from './DeviceList'; diff --git a/web/packages/teleport/src/DeviceTrust/DeviceTrustLocked.tsx b/web/packages/teleport/src/DeviceTrust/DeviceTrustLocked.tsx index d5188f3782af9..c6ed45ce8055b 100644 --- a/web/packages/teleport/src/DeviceTrust/DeviceTrustLocked.tsx +++ b/web/packages/teleport/src/DeviceTrust/DeviceTrustLocked.tsx @@ -16,160 +16,18 @@ * along with this program. If not, see . */ -import styled from 'styled-components'; +import { Box } from 'design'; -import { Box, Flex, Link } from 'design'; -import Table, { Cell } from 'design/DataTable'; -import { Apple, Linux, Lock, Windows } from 'design/Icon'; -import { IconCircle } from 'design/Icon/IconCircle'; -import { P } from 'design/Text/Text'; +import { FeatureBox } from 'teleport/components/Layout'; -import { ButtonLockedFeature } from 'teleport/components/ButtonLockedFeature'; -import { - FeatureBox, - FeatureHeader, - FeatureHeaderTitle, -} from 'teleport/components/Layout'; -import { - DeviceListProps, - TrustedDeviceOSType, -} from 'teleport/DeviceTrust/types'; -import { CtaEvent } from 'teleport/services/userEvent'; +import { EmptyList } from './EmptyList'; export function DeviceTrustLocked() { return ( - - Trusted Devices - - - null} - fetchStatus={''} - /> + + - - - - -

- Device Trust enables trusted and authenticated device access. When - resources are configured with the Device Trust mode “required”, - Teleport will authenticate the Trusted Device, in addition to - establishing the user's identity and enforcing the necessary roles, - and leaves an audit trail with device information. For more details, - please view{' '} - - Device Trust documentation - - . -

- - - Unlock Device Trust with Teleport Enterprise - - -
); } - -function generateFakeItems(count) { - const items = []; - const osType = ['Windows', 'Linux', 'macOS']; - - for (let i = 0; i < count; i++) { - items.push({ - id: `id-${i}`, - assetTag: `CLFBDAACC${i}`, - enrollStatus: `enroll-status-${i}`, - osType: osType[i % osType.length], - }); - } - - return items; -} - -const FakeDeviceList = ({ - items = [], - pageSize = 20, - fetchStatus = '', - fetchData, -}: DeviceListProps) => { - return ( -
, - }, - { - key: 'assetTag', - headerText: 'Asset Tag', - }, - { - key: 'enrollStatus', - headerText: 'Enroll Status', - }, - ]} - emptyText="No Devices Found" - pagination={{ pageSize }} - fetching={{ onFetchMore: fetchData, fetchStatus }} - /> - ); -}; - -const IconCell = ({ osType }: { osType: TrustedDeviceOSType }) => { - let icon; - switch (osType) { - case 'Windows': - icon = ; - break; - case 'Linux': - icon = ; - break; - default: - icon = ; - } - return ( - - {icon} {osType} - - ); -}; - -const StyledMessageContainer = styled(Flex)` - position: relative; - top: 30%; - left: 50%; - transform: translate(-50%, -50%); - background-color: ${({ theme }) => theme.colors.levels.elevated}; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 24px; - gap: 24px; - width: 600px; - box-shadow: - 0 5px 5px -3px rgba(0, 0, 0, 0.2), - 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12); - border-radius: 8px; -`; diff --git a/web/packages/teleport/src/DeviceTrust/EmptyList.tsx b/web/packages/teleport/src/DeviceTrust/EmptyList.tsx new file mode 100644 index 0000000000000..3b2dcc43a6e0b --- /dev/null +++ b/web/packages/teleport/src/DeviceTrust/EmptyList.tsx @@ -0,0 +1,488 @@ +/** + * 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 { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +import { + Box, + ButtonPrimary, + ButtonSecondary, + Flex, + H1, + H2, + H3, + P1, + P2, + ResourceIcon, + Text, +} from 'design'; +import Table from 'design/DataTable'; +import { Cross, FingerprintSimple, Password, UsbDrive } from 'design/Icon'; +import { MobileCamera } from 'design/Icon/Icons/MobileCamera'; +import { + DetailsTab, + FeatureContainer, + FeatureSlider, +} from 'shared/components/EmptyState/EmptyState'; +import { pluralize } from 'shared/utils/text'; + +import { + renderDescCell, + renderTimeCell, +} from 'teleport/Audit/EventList/EventList'; +import renderTypeCell from 'teleport/Audit/EventList/EventTypeCell'; +import { ButtonLockedFeature } from 'teleport/components/ButtonLockedFeature'; +import cfg from 'teleport/config'; +import { TrustedDevice } from 'teleport/DeviceTrust/types'; +import { makeEvent } from 'teleport/services/audit'; +import { CtaEvent } from 'teleport/services/userEvent'; + +import { DeviceList } from './DeviceList'; + +const maxWidth = '1270px'; + +export const EmptyList = ({ + isEnterprise = false, +}: { + isEnterprise?: boolean; +}) => { + const [currIndex, setCurrIndex] = useState(0); + const [intervalId, setIntervalId] = useState(); + + function handleOnClick(clickedIndex: number) { + clearInterval(intervalId); + setCurrIndex(clickedIndex); + setIntervalId(null); + } + + useEffect(() => { + const id = setInterval(() => { + setCurrIndex(latestIndex => (latestIndex + 1) % 4); + }, 3000); + setIntervalId(id); + return () => clearInterval(id); + }, []); + + return ( + + +

What are Trusted Devices?

+ + Device trust reduces the attack surface by enforcing that only + trusted, registered devices can access your Teleport cluster. + +
+ + + + handleOnClick(0)} + title="Guarantee the provenance of the machines accessing your infrastructure." + description="Teleport uses security devices - TPMs on Windows and Linux and secure enclaves on Macs to give every device a cryptographic identity." + /> + handleOnClick(1)} + title="Reduce the attack surface from the entire internet to a limited fleet of clients." + description="Make sure that only registered, cryptographically verified, and trusted devices can access your infrastructure." + /> + handleOnClick(2)} + title="Tie audit events to the user AND machine accessing the system." + description="Device trust maps the device identity to every audit log event, so you always know which device was used for each action." + /> + handleOnClick(3)} + title="Integrates with your MDM" + description="Auto-enroll and sync device registry from Jamf." + /> + + + {currIndex === 0 && ( + +

Trusted Devices

+ +
+ )} + {currIndex === 1 && ( + + + + )} + {currIndex === 2 && ( + +

Audit Log

+ +
+ )} + {currIndex === 3 && ( + + + + + {/* purposefully "creating" the text ourselves to avoid having a light and dark logo just for text */} + + jamf + + + + + + + + + + + + + + + + + + + )} +
+
+ {/* setting a max width here to keep it "in the center" with the content above instead of with the screen */} + + + {isEnterprise ? ( + <> + + Get Started with JAMF + + + See More Options in Our Docs + + + ) : ( + + Unlock Trusted Devices With Teleport Enterprise + + )} + + +
+ ); +}; + +export const fakeItems: TrustedDevice[] = [ + { + id: 'FWPGP915V', + assetTag: 'FWPGP915V', + osType: 'macOS', + enrollStatus: 'enrolled', + owner: 'mykel', + }, + { + id: 'M7XJR4GK8823', + assetTag: 'M7XJR4GK8823', + osType: 'Windows', + enrollStatus: 'enrolled', + owner: 'lila', + }, + { + id: 'L2FQZ9VH4466', + assetTag: 'L2FQZ9VH4466', + osType: 'Linux', + enrollStatus: 'enrolled', + owner: 'bart', + }, + { + id: 'N8EYW1DP7732', + assetTag: 'N8EYW1DP7732', + osType: 'Linux', + enrollStatus: 'not enrolled', + owner: 'rafao', + }, + { + id: 'K5BHP6CT5598', + assetTag: 'K5BHP6CT5598', + osType: 'Windows', + enrollStatus: 'not enrolled', + owner: 'gzz', + }, + { + id: 'Y3RSL7FJ2104', + assetTag: 'Y3RSL7FJ2104', + osType: 'macOS', + enrollStatus: 'enrolled', + owner: 'ryry', + }, +]; + +const List = () => { + return ( + null} + fetchStatus={'disabled'} + /> + ); +}; + +const auditEvents = [ + { + cert_type: 'user', + code: 'TC000I', + event: 'cert.create', + identity: { + user: 'lisa', + }, + time: '2024-02-04T19:43:23.529Z', + }, + { + cluster_name: 'im-a-cluster-name', + code: 'TV006I', + event: 'device.authenticate', + success: true, + time: '2024-02-04T19:43:22.529Z', + uid: 'fa279611-91d8-47b5-9fad-b8ea3e5286e0', + user: 'lisa', + }, + { + cert_type: 'user', + code: 'TC000I', + event: 'cert.create', + identity: { + user: 'isabelle', + }, + time: '2024-02-04T19:43:21.529Z', + }, + { + cluster_name: 'zarq', + code: 'T1016I', + time: '2024-02-04T19:43:20.529Z', + uid: '815bbcf4-fb05-4e08-917c-7259e9332d69', + user: 'isabelle', + }, +].map(makeEvent); + +const AuditList = () => { + return ( +
renderTypeCell(ev), + }, + { + key: 'message', + headerText: 'Description', + render: renderDescCell, + }, + { + key: 'time', + headerText: 'Created (UTC)', + isSortable: true, + render: renderTimeCell, + }, + ]} + emptyText={'No Events Found'} + /> + ); +}; + +const FadedTable = ({ children }) => { + return ( + +

Trusted Devices

+ + p.theme.radii[3]}px; + background-color: rgba(0, 0, 0, 0.5); + `} + > + {children} + +
+ ); +}; + +const AccessDeniedCard = () => { + return ( + p.theme.radii[3]}px; + background-color: ${props => props.theme.colors.levels.surface}; + `} + > + p.theme.colors.spotBackground[0]}; + position: relative; + justify-content: center; + align-items: center; + padding: ${p => p.theme.space[2]}px; + border-radius: ${p => p.theme.radii[3]}px; + `} + > + + p.theme.colors.buttons.warning.default}; + bottom: -12px; + right: -12px; + `} + > + + + +

Access Denied

+ This device is not registered for access. +
+ ); +}; + +export function FeatureLimitBlurb({ limit = 1 }: { limit: number }) { + if (limit === 0) { + // unlimited access + return null; + } + + const listText = pluralize(limit, 'List'); + return ( + + + + Your current plan supports {limit} free Access {listText}. + + + + Want additional Access Lists?{' '} + + Contact Sales + + + + ); +} + +const PreviewBox = styled(Box)` + margin-left: ${p => p.theme.space[5]}px; + width: 675px; + position: relative; + padding: 12px; + border-radius: ${p => p.theme.radii[3]}px; +`; + +const JamfCard = styled(Flex)` + justify-content: center; + align-items: center; + padding: ${p => p.theme.space[5]}px; + border-radius: ${p => p.theme.radii[3]}px; + background-color: ${p => p.theme.colors.levels.surface}; +`; + +const IconCard = styled(Flex)` + justify-content: center; + align-items: center; + cursor: pointer; + height: ${p => p.theme.space[5]}px; + width: ${p => p.theme.space[5]}px; + padding: ${p => p.theme.space[5]}px; + border-radius: ${p => p.theme.radii[3]}px; + background-color: ${p => p.theme.colors.levels.surface}; + color: ${p => p.theme.colors.text.main}; + &:hover { + background-color: ${p => p.theme.colors.interactive.solid.primary.hover}; + color: ${p => p.theme.colors.text.primaryInverse}; + } + transition: all 0.1s; +`; diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index 94b92b0150d97..6ea4b119aa691 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -619,7 +619,10 @@ class FeatureDeviceTrust implements TeleportFeature { }; hasAccess(flags: FeatureFlags) { - return flags.deviceTrust; + if (cfg.hideInaccessibleFeatures) { + return flags.deviceTrust; + } + return true; } navigationItem = {