From 9c314ad40f945a17b02035eee128a19874121c28 Mon Sep 17 00:00:00 2001 From: Bartosz Leper Date: Fri, 20 Dec 2024 20:27:38 +0100 Subject: [PATCH] Use consistent naming in the standard role editor (#50351) (#50491) --- .../RoleEditor/StandardEditor/AccessRules.tsx | 6 +- .../StandardEditor/MetadataSection.tsx | 6 +- .../StandardEditor/Resources.test.tsx | 89 +++++++------- .../RoleEditor/StandardEditor/Resources.tsx | 84 +++++++------- .../StandardEditor/StandardEditor.test.tsx | 4 +- .../StandardEditor/StandardEditor.tsx | 65 ++++++----- .../StandardEditor/StatefulSection.tsx | 18 +-- .../RoleEditor/StandardEditor/sections.tsx | 2 +- .../StandardEditor/standardmodel.test.ts | 48 ++++---- .../StandardEditor/standardmodel.ts | 109 +++++++++--------- .../RoleEditor/StandardEditor/validation.ts | 66 +++++------ 11 files changed, 254 insertions(+), 243 deletions(-) diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/AccessRules.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/AccessRules.tsx index 78b680e21e8b3..dc42ed12bd12a 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/AccessRules.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/AccessRules.tsx @@ -38,7 +38,7 @@ import { RuleModel, verbOptions, } from './standardmodel'; -import { Section, SectionProps } from './sections'; +import { SectionBox, SectionProps } from './sections'; export function AccessRules({ value, @@ -86,7 +86,7 @@ function AccessRule({ }) { const { resources, verbs } = value; return ( -
-
+ ); } diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/MetadataSection.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/MetadataSection.tsx index 8b3c072abcf2a..40a199bad0cb9 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/MetadataSection.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/MetadataSection.tsx @@ -24,7 +24,7 @@ import Text from 'design/Text'; import { LabelsInput } from 'teleport/components/LabelsInput'; -import { Section, SectionProps } from './sections'; +import { SectionBox, SectionProps } from './sections'; import { MetadataModel } from './standardmodel'; import { MetadataValidationResult } from './validation'; @@ -34,7 +34,7 @@ export const MetadataSection = ({ validation, onChange, }: SectionProps) => ( -
onChange?.({ ...value, labels })} rule={precomputed(validation.fields.labels)} /> -
+ ); diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx index da52de8befdce..a866d881b040b 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.test.tsx @@ -22,36 +22,39 @@ import { Validator } from 'shared/components/Validation'; import selectEvent from 'react-select-event'; import { - AppAccessSpec, - DatabaseAccessSpec, - KubernetesAccessSpec, - newAccessSpec, - ServerAccessSpec, - WindowsDesktopAccessSpec, + ServerAccess, + newResourceAccess, + KubernetesAccess, + AppAccess, + DatabaseAccess, + WindowsDesktopAccess, } from './standardmodel'; -import { AccessSpecValidationResult, validateAccessSpec } from './validation'; import { - ServerAccessSpecSection, - KubernetesAccessSpecSection, - AppAccessSpecSection, - DatabaseAccessSpecSection, - WindowsDesktopAccessSpecSection, + ResourceAccessValidationResult, + validateResourceAccess, +} from './validation'; +import { + ServerAccessSection, + KubernetesAccessSection, + AppAccessSection, + DatabaseAccessSection, + WindowsDesktopAccessSection, } from './Resources'; import { StatefulSection } from './StatefulSection'; -describe('ServerAccessSpecSection', () => { +describe('ServerAccessSection', () => { const setup = () => { const onChange = jest.fn(); let validator: Validator; render( - - component={ServerAccessSpecSection} - defaultValue={newAccessSpec('node')} + + component={ServerAccessSection} + defaultValue={newResourceAccess('node')} onChange={onChange} validatorRef={v => { validator = v; }} - validate={validateAccessSpec} + validate={validateResourceAccess} /> ); return { user: userEvent.setup(), onChange, validator }; @@ -80,7 +83,7 @@ describe('ServerAccessSpecSection', () => { expect.objectContaining({ label: 'root', value: 'root' }), expect.objectContaining({ label: 'some-user', value: 'some-user' }), ], - } as ServerAccessSpec); + } as ServerAccess); }); test('validation', async () => { @@ -99,25 +102,25 @@ describe('ServerAccessSpecSection', () => { }); }); -describe('KubernetesAccessSpecSection', () => { +describe('KubernetesAccessSection', () => { const setup = () => { const onChange = jest.fn(); let validator: Validator; render( - - component={KubernetesAccessSpecSection} - defaultValue={newAccessSpec('kube_cluster')} + + component={KubernetesAccessSection} + defaultValue={newResourceAccess('kube_cluster')} onChange={onChange} validatorRef={v => { validator = v; }} - validate={validateAccessSpec} + validate={validateResourceAccess} /> ); return { user: userEvent.setup(), onChange, validator }; }; - test('editing the spec', async () => { + test('editing', async () => { const { user, onChange } = setup(); await selectEvent.create(screen.getByLabelText('Groups'), 'group1', { @@ -167,7 +170,7 @@ describe('KubernetesAccessSpecSection', () => { ], }, ], - } as KubernetesAccessSpec); + } as KubernetesAccess); }); test('adding and removing resources', async () => { @@ -242,19 +245,19 @@ describe('KubernetesAccessSpecSection', () => { }); }); -describe('AppAccessSpecSection', () => { +describe('AppAccessSection', () => { const setup = () => { const onChange = jest.fn(); let validator: Validator; render( - - component={AppAccessSpecSection} - defaultValue={newAccessSpec('app')} + + component={AppAccessSection} + defaultValue={newResourceAccess('app')} onChange={onChange} validatorRef={v => { validator = v; }} - validate={validateAccessSpec} + validate={validateResourceAccess} /> ); return { user: userEvent.setup(), onChange, validator }; @@ -314,7 +317,7 @@ describe('AppAccessSpecSection', () => { '{{internal.gcp_service_accounts}}', 'admin@some-project.iam.gserviceaccount.com', ], - } as AppAccessSpec); + } as AppAccess); }); test('validation', async () => { @@ -348,19 +351,19 @@ describe('AppAccessSpecSection', () => { }); }); -describe('DatabaseAccessSpecSection', () => { +describe('DatabaseAccessSection', () => { const setup = () => { const onChange = jest.fn(); let validator: Validator; render( - - component={DatabaseAccessSpecSection} - defaultValue={newAccessSpec('db')} + + component={DatabaseAccessSection} + defaultValue={newResourceAccess('db')} onChange={onChange} validatorRef={v => { validator = v; }} - validate={validateAccessSpec} + validate={validateResourceAccess} /> ); return { user: userEvent.setup(), onChange, validator }; @@ -395,7 +398,7 @@ describe('DatabaseAccessSpecSection', () => { expect.objectContaining({ value: '{{internal.db_users}}' }), expect.objectContaining({ label: 'mary', value: 'mary' }), ], - } as DatabaseAccessSpec); + } as DatabaseAccess); }); test('validation', async () => { @@ -414,19 +417,19 @@ describe('DatabaseAccessSpecSection', () => { }); }); -describe('WindowsDesktopAccessSpecSection', () => { +describe('WindowsDesktopAccessSection', () => { const setup = () => { const onChange = jest.fn(); let validator: Validator; render( - - component={WindowsDesktopAccessSpecSection} - defaultValue={newAccessSpec('windows_desktop')} + + component={WindowsDesktopAccessSection} + defaultValue={newResourceAccess('windows_desktop')} onChange={onChange} validatorRef={v => { validator = v; }} - validate={validateAccessSpec} + validate={validateResourceAccess} /> ); return { user: userEvent.setup(), onChange, validator }; @@ -447,7 +450,7 @@ describe('WindowsDesktopAccessSpecSection', () => { expect.objectContaining({ value: '{{internal.windows_logins}}' }), expect.objectContaining({ label: 'julio', value: 'julio' }), ], - } as WindowsDesktopAccessSpec); + } as WindowsDesktopAccess); }); test('validation', async () => { diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx index 57315a48b122b..65f1281e64baa 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/Resources.tsx @@ -33,33 +33,33 @@ import styled, { useTheme } from 'styled-components'; import { LabelsInput } from 'teleport/components/LabelsInput'; -import { SectionProps, Section } from './sections'; +import { SectionProps, SectionBox } from './sections'; import { - AccessSpecKind, - AccessSpec, - ServerAccessSpec, - KubernetesAccessSpec, + ResourceAccessKind, + ResourceAccess, + ServerAccess, + KubernetesAccess, newKubernetesResourceModel, KubernetesResourceModel, kubernetesResourceKindOptions, kubernetesVerbOptions, - AppAccessSpec, - DatabaseAccessSpec, - WindowsDesktopAccessSpec, + AppAccess, + DatabaseAccess, + WindowsDesktopAccess, } from './standardmodel'; import { - AccessSpecValidationResult, - ServerSpecValidationResult, - KubernetesSpecValidationResult, + ResourceAccessValidationResult, + ServerAccessValidationResult, + KubernetesAccessValidationResult, KubernetesResourceValidationResult, - AppSpecValidationResult, - DatabaseSpecValidationResult, - WindowsDesktopSpecValidationResult, + AppAccessValidationResult, + DatabaseAccessValidationResult, + WindowsDesktopAccessValidationResult, } from './validation'; -/** Maps access specification kind to UI component configuration. */ -export const specSections: Record< - AccessSpecKind, +/** Maps resource access kind to UI component configuration. */ +export const resourceAccessSections: Record< + ResourceAccessKind, { title: string; tooltip: string; @@ -69,37 +69,37 @@ export const specSections: Record< kube_cluster: { title: 'Kubernetes', tooltip: 'Configures access to Kubernetes clusters', - component: KubernetesAccessSpecSection, + component: KubernetesAccessSection, }, node: { title: 'Servers', tooltip: 'Configures access to SSH servers', - component: ServerAccessSpecSection, + component: ServerAccessSection, }, app: { title: 'Applications', tooltip: 'Configures access to applications', - component: AppAccessSpecSection, + component: AppAccessSection, }, db: { title: 'Databases', tooltip: 'Configures access to databases', - component: DatabaseAccessSpecSection, + component: DatabaseAccessSection, }, windows_desktop: { title: 'Windows Desktops', tooltip: 'Configures access to Windows desktops', - component: WindowsDesktopAccessSpecSection, + component: WindowsDesktopAccessSection, }, }; /** - * A generic access spec section. Details are rendered by components from the - * `specSections` map. + * A generic resource section. Details are rendered by components from the + * `resourceAccessSections` map. */ -export const AccessSpecSection = < - T extends AccessSpec, - V extends AccessSpecValidationResult, +export const ResourceAccessSection = < + T extends ResourceAccess, + V extends ResourceAccessValidationResult, >({ value, isProcessing, @@ -109,9 +109,13 @@ export const AccessSpecSection = < }: SectionProps & { onRemove?(): void; }) => { - const { component: Body, title, tooltip } = specSections[value.kind]; + const { + component: Body, + title, + tooltip, + } = resourceAccessSections[value.kind]; return ( -
-
+ ); }; -export function ServerAccessSpecSection({ +export function ServerAccessSection({ value, isProcessing, validation, onChange, -}: SectionProps) { +}: SectionProps) { return ( <> @@ -165,12 +169,12 @@ export function ServerAccessSpecSection({ ); } -export function KubernetesAccessSpecSection({ +export function KubernetesAccessSection({ value, isProcessing, validation, onChange, -}: SectionProps) { +}: SectionProps) { return ( <> ) { +}: SectionProps) { return ( @@ -369,12 +373,12 @@ export function AppAccessSpecSection({ ); } -export function DatabaseAccessSpecSection({ +export function DatabaseAccessSection({ value, isProcessing, validation, onChange, -}: SectionProps) { +}: SectionProps) { return ( <> @@ -443,12 +447,12 @@ export function DatabaseAccessSpecSection({ ); } -export function WindowsDesktopAccessSpecSection({ +export function WindowsDesktopAccessSection({ value, isProcessing, validation, onChange, -}: SectionProps) { +}: SectionProps) { return ( <> diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx index 525744c64146d..44d360ddeaf34 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.test.tsx @@ -60,7 +60,7 @@ test('adding and removing sections', async () => { expect(getAllSectionNames()).toEqual([]); await user.click( - screen.getByRole('button', { name: 'Add New Specifications' }) + screen.getByRole('button', { name: 'Add New Resource Access' }) ); expect(getAllMenuItemNames()).toEqual([ 'Kubernetes', @@ -74,7 +74,7 @@ test('adding and removing sections', async () => { expect(getAllSectionNames()).toEqual(['Servers']); await user.click( - screen.getByRole('button', { name: 'Add New Specifications' }) + screen.getByRole('button', { name: 'Add New Resource Access' }) ); expect(getAllMenuItemNames()).toEqual([ 'Kubernetes', diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.tsx index 8b4eecb36b365..d70c88f52e983 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StandardEditor.tsx @@ -33,16 +33,16 @@ import { hasModifiedFields, RoleEditorModel, StandardEditorModel, - AccessSpecKind, - AccessSpec, - newAccessSpec, + ResourceAccessKind, + ResourceAccess, + newResourceAccess, RuleModel, OptionsModel, } from './standardmodel'; import { validateRoleEditorModel } from './validation'; import { RequiresResetToStandard } from './RequiresResetToStandard'; import { MetadataSection } from './MetadataSection'; -import { AccessSpecSection, specSections } from './Resources'; +import { ResourceAccessSection, resourceAccessSections } from './Resources'; import { AccessRules } from './AccessRules'; import { Options } from './Options'; @@ -71,9 +71,9 @@ export const StandardEditor = ({ const { roleModel } = standardEditorModel; const validation = validateRoleEditorModel(roleModel); - /** All spec kinds except those that are already in the role. */ - const allowedSpecKinds = allAccessSpecKinds.filter(k => - roleModel.accessSpecs.every(as => as.kind !== k) + /** All resource access kinds except those that are already in the role. */ + const allowedResourceAccessKinds = allResourceAccessKinds.filter(k => + roleModel.resources.every(as => as.kind !== k) ); enum StandardEditorTab { @@ -112,29 +112,29 @@ export const StandardEditor = ({ }); } - function addAccessSpec(kind: AccessSpecKind) { + function addResourceAccess(kind: ResourceAccessKind) { handleChange({ ...standardEditorModel.roleModel, - accessSpecs: [ - ...standardEditorModel.roleModel.accessSpecs, - newAccessSpec(kind), + resources: [ + ...standardEditorModel.roleModel.resources, + newResourceAccess(kind), ], }); } - function removeAccessSpec(kind: AccessSpecKind) { + function removeResourceAccess(kind: ResourceAccessKind) { handleChange({ ...standardEditorModel.roleModel, - accessSpecs: standardEditorModel.roleModel.accessSpecs.filter( + resources: standardEditorModel.roleModel.resources.filter( s => s.kind !== kind ), }); } - function setAccessSpec(value: AccessSpec) { + function setResourceAccess(value: ResourceAccess) { handleChange({ ...standardEditorModel.roleModel, - accessSpecs: standardEditorModel.roleModel.accessSpecs.map(original => + resources: standardEditorModel.roleModel.resources.map(original => original.kind === value.kind ? value : original ), }); @@ -185,7 +185,7 @@ export const StandardEditor = ({ controls: resourcesTabId, status: validator.state.validating && - validation.accessSpecs.some(s => !s.valid) + validation.resources.some(s => !s.valid) ? validationErrorTabStatus : undefined, }, @@ -238,16 +238,16 @@ export const StandardEditor = ({ }} > - {roleModel.accessSpecs.map((spec, i) => { - const validationResult = validation.accessSpecs[i]; + {roleModel.resources.map((res, i) => { + const validationResult = validation.resources[i]; return ( - setAccessSpec(value)} - onRemove={() => removeAccessSpec(spec.kind)} + onChange={value => setResourceAccess(value)} + onRemove={() => removeResourceAccess(res.kind)} /> ); })} @@ -266,18 +266,22 @@ export const StandardEditor = ({ buttonText={ <> - Add New Specifications + Add New Resource Access } buttonProps={{ size: 'medium', fill: 'filled', - disabled: isProcessing || allowedSpecKinds.length === 0, + disabled: + isProcessing || allowedResourceAccessKinds.length === 0, }} > - {allowedSpecKinds.map(kind => ( - addAccessSpec(kind)}> - {specSections[kind].title} + {allowedResourceAccessKinds.map(kind => ( + addResourceAccess(kind)} + > + {resourceAccessSections[kind].title} ))} @@ -332,9 +336,10 @@ const validationErrorTabStatus = { } as const; /** - * All access spec kinds, in order of appearance in the resource kind dropdown. + * All resource access kinds, in order of appearance in the resource kind + * dropdown. */ -const allAccessSpecKinds: AccessSpecKind[] = [ +const allResourceAccessKinds: ResourceAccessKind[] = [ 'kube_cluster', 'node', 'app', diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StatefulSection.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StatefulSection.tsx index 24642b574f840..3be195cbfa696 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StatefulSection.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/StatefulSection.tsx @@ -23,20 +23,20 @@ import Validation, { Validator } from 'shared/components/Validation'; import { SectionProps } from './sections'; /** A helper for testing editor section components. */ -export function StatefulSection({ +export function StatefulSection({ defaultValue, component: Component, onChange, validatorRef, validate, }: { - defaultValue: Spec; - component: React.ComponentType>; - onChange(spec: Spec): void; + defaultValue: Model; + component: React.ComponentType>; + onChange(model: Model): void; validatorRef?(v: Validator): void; - validate(arg: Spec): ValidationResult; + validate(arg: Model): ValidationResult; }) { - const [model, setModel] = useState(defaultValue); + const [model, setModel] = useState(defaultValue); const validation = validate(model); return ( @@ -47,9 +47,9 @@ export function StatefulSection({ value={model} validation={validation} isProcessing={false} - onChange={spec => { - setModel(spec); - onChange(spec); + onChange={model => { + setModel(model); + onChange(model); }} /> ); diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/sections.tsx b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/sections.tsx index 45e4be64d74b8..2af2808516f6c 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/sections.tsx +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/sections.tsx @@ -38,7 +38,7 @@ export type SectionProps = { * A wrapper for editor section. Its responsibility is rendering a header, * expanding, collapsing, and removing the section. */ -export const Section = ({ +export const SectionBox = ({ title, tooltip, children, diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts index 1ce78dc4edf96..14ab1fbdc19b5 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.test.ts @@ -32,7 +32,7 @@ import { Label as UILabel } from 'teleport/components/LabelsInput/LabelsInput'; import { Labels } from 'teleport/services/resources'; import { - KubernetesAccessSpec, + KubernetesAccess, labelsModelToLabels, labelsToModel, RoleEditorModel, @@ -46,7 +46,7 @@ const minimalRole = () => const minimalRoleModel = (): RoleEditorModel => ({ metadata: { name: 'foobar', labels: [] }, - accessSpecs: [], + resources: [], rules: [], requiresReset: false, options: { @@ -102,7 +102,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, { - name: 'server access spec', + name: 'server access', role: { ...minimalRole(), spec: { @@ -115,7 +115,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, model: { ...minimalRoleModel(), - accessSpecs: [ + resources: [ { kind: 'node', labels: [{ name: 'foo', value: 'bar' }], @@ -130,7 +130,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, { - name: 'app access spec', + name: 'app access', role: { ...minimalRole(), spec: { @@ -154,7 +154,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, model: { ...minimalRoleModel(), - accessSpecs: [ + resources: [ { kind: 'app', labels: [{ name: 'foo', value: 'bar' }], @@ -176,7 +176,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, { - name: 'database access spec', + name: 'database access', role: { ...minimalRole(), spec: { @@ -191,7 +191,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, model: { ...minimalRoleModel(), - accessSpecs: [ + resources: [ { kind: 'db', labels: [{ name: 'env', value: 'prod' }], @@ -213,7 +213,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, { - name: 'Windows desktop access spec', + name: 'Windows desktop access', role: { ...minimalRole(), spec: { @@ -226,7 +226,7 @@ describe.each<{ name: string; role: Role; model: RoleEditorModel }>([ }, model: { ...minimalRoleModel(), - accessSpecs: [ + resources: [ { kind: 'windows_desktop', labels: [{ name: 'os', value: 'WindowsForWorkgroups' }], @@ -305,8 +305,8 @@ describe('roleToRoleEditorModel', () => { ...minimalRoleModel(), requiresReset: true, }; - // Same as newAccessSpec('kube_cluster'), but without default groups. - const newKubeClusterAccessSpec = (): KubernetesAccessSpec => ({ + // Same as newResourceAccess('kube_cluster'), but without default groups. + const newKubeClusterResourceAccess = (): KubernetesAccess => ({ kind: 'kube_cluster', groups: [], labels: [], @@ -362,9 +362,9 @@ describe('roleToRoleEditorModel', () => { } as Role, model: { ...roleModelWithReset, - accessSpecs: [ + resources: [ { - ...newKubeClusterAccessSpec(), + ...newKubeClusterResourceAccess(), resources: [expect.any(Object)], }, ], @@ -388,9 +388,9 @@ describe('roleToRoleEditorModel', () => { } as Role, model: { ...roleModelWithReset, - accessSpecs: [ + resources: [ { - ...newKubeClusterAccessSpec(), + ...newKubeClusterResourceAccess(), resources: [ expect.objectContaining({ kind: { value: 'job', label: 'Job' } }), ], @@ -418,9 +418,9 @@ describe('roleToRoleEditorModel', () => { } as Role, model: { ...roleModelWithReset, - accessSpecs: [ + resources: [ { - ...newKubeClusterAccessSpec(), + ...newKubeClusterResourceAccess(), resources: [ expect.objectContaining({ verbs: [{ value: 'get', label: 'get' }], @@ -697,7 +697,7 @@ describe('roleToRoleEditorModel', () => { // This case has to be tested separately because of dynamic resource ID // generation. - it('creates a Kubernetes access spec', () => { + it('creates Kubernetes access', () => { const minRole = minimalRole(); expect( roleToRoleEditorModel({ @@ -725,7 +725,7 @@ describe('roleToRoleEditorModel', () => { }) ).toEqual({ ...minimalRoleModel(), - accessSpecs: [ + resources: [ { kind: 'kube_cluster', groups: [ @@ -758,7 +758,7 @@ describe('roleToRoleEditorModel', () => { }); // Make sure that some fields are optional. - it('creates a minimal app access spec', () => { + it('creates minimal app access', () => { const minRole = minimalRole(); expect( roleToRoleEditorModel({ @@ -772,7 +772,7 @@ describe('roleToRoleEditorModel', () => { }) ).toEqual({ ...minimalRoleModel(), - accessSpecs: [ + resources: [ { kind: 'app', labels: [{ name: 'foo', value: 'bar' }], @@ -858,12 +858,12 @@ describe('roleEditorModelToRole', () => { // This case has to be tested separately because of dynamic resource ID // generation. - it('converts a Kubernetes access spec', () => { + it('converts Kubernetes access', () => { const minRole = minimalRole(); expect( roleEditorModelToRole({ ...minimalRoleModel(), - accessSpecs: [ + resources: [ { kind: 'kube_cluster', groups: [ diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts index acd01eff735e6..64d3296e0e1a0 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/standardmodel.ts @@ -56,7 +56,7 @@ export type StandardEditorModel = { */ export type RoleEditorModel = { metadata: MetadataModel; - accessSpecs: AccessSpec[]; + resources: ResourceAccess[]; rules: RuleModel[]; options: OptionsModel; /** @@ -75,19 +75,18 @@ export type MetadataModel = { labels: UILabel[]; }; -/** A model for access specifications section. */ -export type AccessSpec = - | KubernetesAccessSpec - | ServerAccessSpec - | AppAccessSpec - | DatabaseAccessSpec - | WindowsDesktopAccessSpec; +/** A model for resource section. */ +export type ResourceAccess = + | KubernetesAccess + | ServerAccess + | AppAccess + | DatabaseAccess + | WindowsDesktopAccess; /** - * A base for all access specification section models. Contains a type - * discriminator field. + * A base for all resource section models. Contains a type discriminator field. */ -type AccessSpecBase = { +type ResourceAccessBase = { /** * Determines kind of resource that is accessed using this spec. Intended to * be mostly consistent with UnifiedResources.kind, but that has no real @@ -97,15 +96,15 @@ type AccessSpecBase = { kind: T; }; -export type AccessSpecKind = +export type ResourceAccessKind = | 'node' | 'kube_cluster' | 'app' | 'db' | 'windows_desktop'; -/** Model for the Kubernetes access specification section. */ -export type KubernetesAccessSpec = AccessSpecBase<'kube_cluster'> & { +/** Model for the Kubernetes resource section. */ +export type KubernetesAccess = ResourceAccessBase<'kube_cluster'> & { groups: readonly Option[]; labels: UILabel[]; resources: KubernetesResourceModel[]; @@ -226,27 +225,27 @@ export const verbOptions: VerbOption[] = ( ).map(stringToOption); const verbOptionsMap = optionsToMap(verbOptions); -/** Model for the server access specification section. */ -export type ServerAccessSpec = AccessSpecBase<'node'> & { +/** Model for the server resource access section. */ +export type ServerAccess = ResourceAccessBase<'node'> & { labels: UILabel[]; logins: readonly Option[]; }; -export type AppAccessSpec = AccessSpecBase<'app'> & { +export type AppAccess = ResourceAccessBase<'app'> & { labels: UILabel[]; awsRoleARNs: string[]; azureIdentities: string[]; gcpServiceAccounts: string[]; }; -export type DatabaseAccessSpec = AccessSpecBase<'db'> & { +export type DatabaseAccess = ResourceAccessBase<'db'> & { labels: UILabel[]; names: readonly Option[]; users: readonly Option[]; roles: readonly Option[]; }; -export type WindowsDesktopAccessSpec = AccessSpecBase<'windows_desktop'> & { +export type WindowsDesktopAccess = ResourceAccessBase<'windows_desktop'> & { labels: UILabel[]; logins: readonly Option[]; }; @@ -342,15 +341,15 @@ export function newRole(): Role { }; } -export function newAccessSpec(kind: 'node'): ServerAccessSpec; -export function newAccessSpec(kind: 'kube_cluster'): KubernetesAccessSpec; -export function newAccessSpec(kind: 'app'): AppAccessSpec; -export function newAccessSpec(kind: 'db'): DatabaseAccessSpec; -export function newAccessSpec( +export function newResourceAccess(kind: 'node'): ServerAccess; +export function newResourceAccess(kind: 'kube_cluster'): KubernetesAccess; +export function newResourceAccess(kind: 'app'): AppAccess; +export function newResourceAccess(kind: 'db'): DatabaseAccess; +export function newResourceAccess( kind: 'windows_desktop' -): WindowsDesktopAccessSpec; -export function newAccessSpec(kind: AccessSpecKind): AppAccessSpec; -export function newAccessSpec(kind: AccessSpecKind): AccessSpec { +): WindowsDesktopAccess; +export function newResourceAccess(kind: ResourceAccessKind): AppAccess; +export function newResourceAccess(kind: ResourceAccessKind): ResourceAccess { switch (kind) { case 'node': return { @@ -428,7 +427,7 @@ export function roleToRoleEditorModel( metadata; const { allow, deny, options, ...unsupportedSpecs } = spec; const { - accessSpecs, + resources, rules, requiresReset: allowRequiresReset, } = roleConditionsToModel(allow); @@ -442,7 +441,7 @@ export function roleToRoleEditorModel( revision: originalRole?.metadata?.revision, labels: labelsToModel(labels), }, - accessSpecs, + resources, rules, options: optionsModel, requiresReset: @@ -461,11 +460,11 @@ export function roleToRoleEditorModel( /** * Converts a `RoleConditions` instance (an "allow" or "deny" section, to be - * specific) to a list of access specification models. + * specific) to a part of the role editor model. */ function roleConditionsToModel( conditions: RoleConditions -): Pick { +): Pick { const { node_labels, logins, @@ -492,12 +491,12 @@ function roleConditionsToModel( ...unsupportedConditions } = conditions; - const accessSpecs: AccessSpec[] = []; + const resources: ResourceAccess[] = []; const nodeLabelsModel = labelsToModel(node_labels); const nodeLoginsModel = stringsToOptions(logins ?? []); if (someNonEmpty(nodeLabelsModel, nodeLoginsModel)) { - accessSpecs.push({ + resources.push({ kind: 'node', labels: nodeLabelsModel, logins: nodeLoginsModel, @@ -511,7 +510,7 @@ function roleConditionsToModel( requiresReset: kubernetesResourcesRequireReset, } = kubernetesResourcesToModel(kubernetes_resources); if (someNonEmpty(kubeGroupsModel, kubeLabelsModel, kubeResourcesModel)) { - accessSpecs.push({ + resources.push({ kind: 'kube_cluster', groups: kubeGroupsModel, labels: kubeLabelsModel, @@ -531,7 +530,7 @@ function roleConditionsToModel( gcpServiceAccountsModel ) ) { - accessSpecs.push({ + resources.push({ kind: 'app', labels: appLabelsModel, awsRoleARNs: awsRoleARNsModel, @@ -545,7 +544,7 @@ function roleConditionsToModel( const dbUsersModel = db_users ?? []; const dbRolesModel = db_roles ?? []; if (someNonEmpty(dbLabelsModel, dbNamesModel, dbUsersModel, dbRolesModel)) { - accessSpecs.push({ + resources.push({ kind: 'db', labels: dbLabelsModel, names: stringsToOptions(dbNamesModel), @@ -559,7 +558,7 @@ function roleConditionsToModel( windows_desktop_logins ?? [] ); if (someNonEmpty(windowsDesktopLabelsModel, windowsDesktopLoginsModel)) { - accessSpecs.push({ + resources.push({ kind: 'windows_desktop', labels: windowsDesktopLabelsModel, logins: windowsDesktopLoginsModel, @@ -570,7 +569,7 @@ function roleConditionsToModel( rulesToModel(rules); return { - accessSpecs, + resources: resources, rules: rulesModel, requiresReset: kubernetesResourcesRequireReset || @@ -799,18 +798,18 @@ export function roleEditorModelToRole(roleModel: RoleEditorModel): Role { version: roleVersion, }; - for (const spec of roleModel.accessSpecs) { - const { kind } = spec; + for (const res of roleModel.resources) { + const { kind } = res; switch (kind) { case 'node': - role.spec.allow.node_labels = labelsModelToLabels(spec.labels); - role.spec.allow.logins = optionsToStrings(spec.logins); + role.spec.allow.node_labels = labelsModelToLabels(res.labels); + role.spec.allow.logins = optionsToStrings(res.logins); break; case 'kube_cluster': - role.spec.allow.kubernetes_groups = optionsToStrings(spec.groups); - role.spec.allow.kubernetes_labels = labelsModelToLabels(spec.labels); - role.spec.allow.kubernetes_resources = spec.resources.map( + role.spec.allow.kubernetes_groups = optionsToStrings(res.groups); + role.spec.allow.kubernetes_labels = labelsModelToLabels(res.labels); + role.spec.allow.kubernetes_resources = res.resources.map( ({ kind, name, namespace, verbs }) => ({ kind: kind.value, name, @@ -821,24 +820,24 @@ export function roleEditorModelToRole(roleModel: RoleEditorModel): Role { break; case 'app': - role.spec.allow.app_labels = labelsModelToLabels(spec.labels); - role.spec.allow.aws_role_arns = spec.awsRoleARNs; - role.spec.allow.azure_identities = spec.azureIdentities; - role.spec.allow.gcp_service_accounts = spec.gcpServiceAccounts; + role.spec.allow.app_labels = labelsModelToLabels(res.labels); + role.spec.allow.aws_role_arns = res.awsRoleARNs; + role.spec.allow.azure_identities = res.azureIdentities; + role.spec.allow.gcp_service_accounts = res.gcpServiceAccounts; break; case 'db': - role.spec.allow.db_labels = labelsModelToLabels(spec.labels); - role.spec.allow.db_names = optionsToStrings(spec.names); - role.spec.allow.db_users = optionsToStrings(spec.users); - role.spec.allow.db_roles = optionsToStrings(spec.roles); + role.spec.allow.db_labels = labelsModelToLabels(res.labels); + role.spec.allow.db_names = optionsToStrings(res.names); + role.spec.allow.db_users = optionsToStrings(res.users); + role.spec.allow.db_roles = optionsToStrings(res.roles); break; case 'windows_desktop': role.spec.allow.windows_desktop_labels = labelsModelToLabels( - spec.labels + res.labels ); - role.spec.allow.windows_desktop_logins = optionsToStrings(spec.logins); + role.spec.allow.windows_desktop_logins = optionsToStrings(res.logins); break; default: diff --git a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts index 2cbcc596518f6..d90414360ca4f 100644 --- a/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts +++ b/web/packages/teleport/src/Roles/RoleEditor/StandardEditor/validation.ts @@ -31,7 +31,7 @@ import { KubernetesResourceKind } from 'teleport/services/resources'; import { nonEmptyLabels } from 'teleport/components/LabelsInput/LabelsInput'; import { - AccessSpec, + ResourceAccess, KubernetesResourceModel, MetadataModel, RoleEditorModel, @@ -49,12 +49,12 @@ const kubernetesClusterWideResourceKinds: KubernetesResourceKind[] = [ export function validateRoleEditorModel({ metadata, - accessSpecs, + resources, rules, }: RoleEditorModel) { return { metadata: validateMetadata(metadata), - accessSpecs: accessSpecs.map(validateAccessSpec), + resources: resources.map(validateResourceAccess), rules: rules.map(validateAccessRule), }; } @@ -71,32 +71,32 @@ export type MetadataValidationResult = RuleSetValidationResult< typeof metadataRules >; -export function validateAccessSpec( - spec: AccessSpec -): AccessSpecValidationResult { - const { kind } = spec; +export function validateResourceAccess( + res: ResourceAccess +): ResourceAccessValidationResult { + const { kind } = res; switch (kind) { case 'kube_cluster': - return runRules(spec, kubernetesValidationRules); + return runRules(res, kubernetesAccessValidationRules); case 'node': - return runRules(spec, serverValidationRules); + return runRules(res, serverAccessValidationRules); case 'app': - return runRules(spec, appSpecValidationRules); + return runRules(res, appAccessValidationRules); case 'db': - return runRules(spec, databaseSpecValidationRules); + return runRules(res, databaseAccessValidationRules); case 'windows_desktop': - return runRules(spec, windowsDesktopSpecValidationRules); + return runRules(res, windowsDesktopAccessValidationRules); default: kind satisfies never; } } -export type AccessSpecValidationResult = - | ServerSpecValidationResult - | KubernetesSpecValidationResult - | AppSpecValidationResult - | DatabaseSpecValidationResult - | WindowsDesktopSpecValidationResult; +export type ResourceAccessValidationResult = + | ServerAccessValidationResult + | KubernetesAccessValidationResult + | AppAccessValidationResult + | DatabaseAccessValidationResult + | WindowsDesktopAccessValidationResult; const validKubernetesResource = (res: KubernetesResourceModel) => () => { const name = requiredField( @@ -118,12 +118,12 @@ export type KubernetesResourceValidationResult = { namespace: ValidationResult; }; -const kubernetesValidationRules = { +const kubernetesAccessValidationRules = { labels: nonEmptyLabels, resources: arrayOf(validKubernetesResource), }; -export type KubernetesSpecValidationResult = RuleSetValidationResult< - typeof kubernetesValidationRules +export type KubernetesAccessValidationResult = RuleSetValidationResult< + typeof kubernetesAccessValidationRules >; const noWildcard = (message: string) => (value: string) => () => { @@ -136,15 +136,15 @@ const noWildcardOptions = (message: string) => (options: Option[]) => () => { return { valid, message: valid ? '' : message }; }; -const serverValidationRules = { +const serverAccessValidationRules = { labels: nonEmptyLabels, logins: noWildcardOptions('Wildcard is not allowed in logins'), }; -export type ServerSpecValidationResult = RuleSetValidationResult< - typeof serverValidationRules +export type ServerAccessValidationResult = RuleSetValidationResult< + typeof serverAccessValidationRules >; -const appSpecValidationRules = { +const appAccessValidationRules = { labels: nonEmptyLabels, awsRoleARNs: arrayOf(noWildcard('Wildcard is not allowed in AWS role ARNs')), azureIdentities: arrayOf( @@ -154,23 +154,23 @@ const appSpecValidationRules = { noWildcard('Wildcard is not allowed in GCP service accounts') ), }; -export type AppSpecValidationResult = RuleSetValidationResult< - typeof appSpecValidationRules +export type AppAccessValidationResult = RuleSetValidationResult< + typeof appAccessValidationRules >; -const databaseSpecValidationRules = { +const databaseAccessValidationRules = { labels: nonEmptyLabels, roles: noWildcardOptions('Wildcard is not allowed in database roles'), }; -export type DatabaseSpecValidationResult = RuleSetValidationResult< - typeof databaseSpecValidationRules +export type DatabaseAccessValidationResult = RuleSetValidationResult< + typeof databaseAccessValidationRules >; -const windowsDesktopSpecValidationRules = { +const windowsDesktopAccessValidationRules = { labels: nonEmptyLabels, }; -export type WindowsDesktopSpecValidationResult = RuleSetValidationResult< - typeof windowsDesktopSpecValidationRules +export type WindowsDesktopAccessValidationResult = RuleSetValidationResult< + typeof windowsDesktopAccessValidationRules >; export const validateAccessRule = (accessRule: RuleModel) =>