Skip to content

Commit

Permalink
REview
Browse files Browse the repository at this point in the history
  • Loading branch information
bl-nero committed Dec 3, 2024
1 parent f420534 commit 17823aa
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 150 deletions.
6 changes: 5 additions & 1 deletion web/packages/teleport/src/Roles/RoleEditor/EditorHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export const EditorHeader = ({
return (
<Flex alignItems="center" mb={3} gap={2}>
<Box flex="1">
<H2>{isCreating ? 'Create a New Role' : role?.metadata.name}</H2>
<H2>
{isCreating
? 'Create a New Role'
: `Edit Role ${role?.metadata.name}`}
</H2>
</Box>
<Box flex="0 0 24px" lineHeight={0}>
{isProcessing && <Indicator size={24} color="text.muted" />}
Expand Down
2 changes: 2 additions & 0 deletions web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export const RoleEditor = ({
onDelete,
}: RoleEditorProps) => {
const idPrefix = useId();
// These IDs are needed to connect accessibility attributes between the
// standard/YAML tab switcher and the switched panels.
const standardEditorId = `${idPrefix}-standard`;
const yamlEditorId = `${idPrefix}-yaml`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import {
roleToRoleEditorModel,
ServerAccessSpec,
StandardEditorModel,
validateAccessSpec,
WindowsDesktopAccessSpec,
} from './standardmodel';
import {
Expand All @@ -49,6 +48,7 @@ import {
StandardEditorProps,
WindowsDesktopAccessSpecSection,
} from './StandardEditor';
import { validateAccessSpec } from './validation';

const TestStandardEditor = (props: Partial<StandardEditorProps>) => {
const ctx = createTeleportContext();
Expand Down
21 changes: 13 additions & 8 deletions web/packages/teleport/src/Roles/RoleEditor/StandardEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ import {
AppAccessSpec,
DatabaseAccessSpec,
WindowsDesktopAccessSpec,
} from './standardmodel';
import {
validateRoleEditorModel,
MetadataValidationResult,
AccessSpecValidationResult,
Expand All @@ -78,7 +80,7 @@ import {
AppSpecValidationResult,
DatabaseSpecValidationResult,
WindowsDesktopSpecValidationResult,
} from './standardmodel';
} from './validation';
import { EditorSaveCancelButton } from './Shared';
import { RequiresResetToStandard } from './RequiresResetToStandard';

Expand Down Expand Up @@ -206,17 +208,20 @@ export const StandardEditor = ({
key: StandardEditorTab.Overview,
title: 'Overview',
controls: overviewTabId,
status: validation.metadata.valid
? undefined
: validationErrorTabStatus,
status:
validator.state.validating && !validation.metadata.valid
? validationErrorTabStatus
: undefined,
},
{
key: StandardEditorTab.Resources,
title: 'Resources',
controls: resourcesTabId,
status: validation.accessSpecs.every(s => s.valid)
? undefined
: validationErrorTabStatus,
status:
validator.state.validating &&
validation.accessSpecs.some(s => !s.valid)
? validationErrorTabStatus
: undefined,
},
{
key: StandardEditorTab.AdminRules,
Expand Down Expand Up @@ -613,7 +618,7 @@ export function KubernetesAccessSpecSection({
<KubernetesResourceView
key={resource.id}
value={resource}
validation={validation.fields.resources.items[index]}
validation={validation.fields.resources.results[index]}
isProcessing={isProcessing}
onChange={newRes =>
onChange?.({
Expand Down
141 changes: 1 addition & 140 deletions web/packages/teleport/src/Roles/RoleEditor/standardmodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,14 @@

import { equalsDeep } from 'shared/utils/highbar';
import { Option } from 'shared/components/Select';
import {
arrayOf,
requiredField,
RuleSetValidationResult,
runRules,
ValidationResult,
} from 'shared/components/Validation/rules';

import {
KubernetesResource,
Labels,
Role,
RoleConditions,
} from 'teleport/services/resources';
import {
nonEmptyLabels,
Label as UILabel,
} from 'teleport/components/LabelsInput/LabelsInput';
import { Label as UILabel } from 'teleport/components/LabelsInput/LabelsInput';
import {
KubernetesResourceKind,
KubernetesVerb,
Expand Down Expand Up @@ -120,14 +110,6 @@ export type KubernetesResourceModel = {
};

type KubernetesResourceKindOption = Option<KubernetesResourceKind, string>;
const kubernetesClusterWideResourceKinds: KubernetesResourceKind[] = [
'namespace',
'kube_node',
'persistentvolume',
'clusterrole',
'clusterrolebinding',
'certificatesigningrequest',
];

/**
* All possible resource kind drop-down options. This array needs to be kept in
Expand Down Expand Up @@ -587,124 +569,3 @@ export function hasModifiedFields(
ignoreUndefined: true,
});
}

export function validateRoleEditorModel({
metadata,
accessSpecs,
}: RoleEditorModel) {
return {
metadata: validateMetadata(metadata),
accessSpecs: accessSpecs.map(validateAccessSpec),
};
}

function validateMetadata(model: MetadataModel): MetadataValidationResult {
return runRules(model, metadataRules);
}

const metadataRules = { name: requiredField('Role name is required') };
export type MetadataValidationResult = RuleSetValidationResult<
typeof metadataRules
>;

export function validateAccessSpec(
spec: AccessSpec
): AccessSpecValidationResult {
const { kind } = spec;
switch (kind) {
case 'kube_cluster':
return runRules(spec, kubernetesValidationRules);
case 'node':
return runRules(spec, serverValidationRules);
case 'app':
return runRules(spec, appSpecValidationRules);
case 'db':
return runRules(spec, databaseSpecValidationRules);
case 'windows_desktop':
return runRules(spec, windowsDesktopSpecValidationRules);
default:
kind satisfies never;
}
}

export type AccessSpecValidationResult =
| ServerSpecValidationResult
| KubernetesSpecValidationResult
| AppSpecValidationResult
| DatabaseSpecValidationResult
| WindowsDesktopSpecValidationResult;

const validKubernetesResource = (res: KubernetesResourceModel) => () => {
const name = requiredField(
'Resource name is required, use "*" for any resource'
)(res.name)();
const namespace = kubernetesClusterWideResourceKinds.includes(res.kind.value)
? { valid: true }
: requiredField('Namespace is required for resources of this kind')(
res.namespace
)();
return {
valid: name.valid && namespace.valid,
name,
namespace,
};
};
export type KubernetesResourceValidationResult = {
name: ValidationResult;
namespace: ValidationResult;
};

const kubernetesValidationRules = {
labels: nonEmptyLabels,
resources: arrayOf(validKubernetesResource),
};
export type KubernetesSpecValidationResult = RuleSetValidationResult<
typeof kubernetesValidationRules
>;

const noWildcard = (message: string) => (value: string) => () => {
const valid = value !== '*';
return { valid, message: valid ? '' : message };
};

const noWildcardOptions = (message: string) => (options: Option[]) => () => {
const valid = options.every(o => o.value !== '*');
return { valid, message: valid ? '' : message };
};

const serverValidationRules = {
labels: nonEmptyLabels,
logins: noWildcardOptions('Wildcard is not allowed in logins'),
};
export type ServerSpecValidationResult = RuleSetValidationResult<
typeof serverValidationRules
>;

const appSpecValidationRules = {
labels: nonEmptyLabels,
awsRoleARNs: arrayOf(noWildcard('Wildcard is not allowed in AWS role ARNs')),
azureIdentities: arrayOf(
noWildcard('Wildcard is not allowed in Azure identities')
),
gcpServiceAccounts: arrayOf(
noWildcard('Wildcard is not allowed in GCP service accounts')
),
};
export type AppSpecValidationResult = RuleSetValidationResult<
typeof appSpecValidationRules
>;

const databaseSpecValidationRules = {
labels: nonEmptyLabels,
roles: noWildcardOptions('Wildcard is not allowed in database roles'),
};
export type DatabaseSpecValidationResult = RuleSetValidationResult<
typeof databaseSpecValidationRules
>;

const windowsDesktopSpecValidationRules = {
labels: nonEmptyLabels,
};
export type WindowsDesktopSpecValidationResult = RuleSetValidationResult<
typeof windowsDesktopSpecValidationRules
>;
Loading

0 comments on commit 17823aa

Please sign in to comment.