Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support more fields in the role editor #51458

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/services/presets.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ func NewSystemAutomaticAccessBotUser() types.User {
// NewPresetEditorRole returns a new pre-defined role for cluster
// editors who can edit cluster configuration resources.
func NewPresetEditorRole() types.Role {
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
marcoandredinis marked this conversation as resolved.
Show resolved Hide resolved
role := &types.RoleV6{
Kind: types.KindRole,
Version: types.V7,
Expand All @@ -116,6 +120,10 @@ func NewPresetEditorRole() types.Role {
},
},
Spec: types.RoleSpecV6{
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
Options: types.RoleOptions{
CertificateFormat: constants.CertificateFormatStandard,
MaxSessionTTL: types.NewDuration(apidefaults.MaxCertDuration),
Expand All @@ -133,6 +141,10 @@ func NewPresetEditorRole() types.Role {
Desktop: types.NewBoolOption(false),
},
},
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
Allow: types.RoleConditions{
Namespaces: []string{apidefaults.Namespace},
Rules: []types.Rule{
Expand Down Expand Up @@ -208,6 +220,10 @@ func NewPresetEditorRole() types.Role {
// NewPresetAccessRole creates a role for users who are allowed to initiate
// interactive sessions.
func NewPresetAccessRole() types.Role {
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
role := &types.RoleV6{
Kind: types.KindRole,
Version: types.V7,
Expand All @@ -220,6 +236,10 @@ func NewPresetAccessRole() types.Role {
},
},
Spec: types.RoleSpecV6{
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
Options: types.RoleOptions{
CertificateFormat: constants.CertificateFormatStandard,
MaxSessionTTL: types.NewDuration(apidefaults.MaxCertDuration),
Expand All @@ -235,6 +255,10 @@ func NewPresetAccessRole() types.Role {
BPF: apidefaults.EnhancedEvents(),
RecordSession: &types.RecordSession{Desktop: types.NewBoolOption(true)},
},
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
Allow: types.RoleConditions{
Namespaces: []string{apidefaults.Namespace},
NodeLabels: types.Labels{types.Wildcard: []string{types.Wildcard}},
Expand Down Expand Up @@ -270,6 +294,10 @@ func NewPresetAccessRole() types.Role {
},
},
}
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
role.SetLogins(types.Allow, []string{teleport.TraitInternalLoginsVariable})
role.SetWindowsLogins(types.Allow, []string{teleport.TraitInternalWindowsLoginsVariable})
role.SetKubeUsers(types.Allow, []string{teleport.TraitInternalKubeUsersVariable})
Expand All @@ -284,6 +312,10 @@ func NewPresetAccessRole() types.Role {
// auditor - someone who can review cluster events and replay sessions,
// but can't initiate interactive sessions or modify configuration.
func NewPresetAuditorRole() types.Role {
// IMPORTANT: Before adding new defaults, please make sure that the
// underlying field is supported by the standard role editor UI. This role
// should be editable with a rich UI, without requiring the user to dive into
// YAML.
role := &types.RoleV6{
Kind: types.KindRole,
Version: types.V7,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('AccessRules', () => {
'list',
'read',
]);
await user.type(screen.getByLabelText('Filter'), 'some-filter');
expect(modelRef).toHaveBeenLastCalledWith([
{
id: expect.any(String),
Expand All @@ -69,6 +70,7 @@ describe('AccessRules', () => {
{ label: 'list', value: 'list' },
{ label: 'read', value: 'read' },
],
where: 'some-filter',
},
] as RuleModel[]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

import { memo } from 'react';
import { components, MultiValueProps } from 'react-select';
import styled from 'styled-components';
import styled, { useTheme } from 'styled-components';

import { ButtonSecondary } from 'design/Button';
import Flex from 'design/Flex';
import { Plus } from 'design/Icon';
import Text from 'design/Text';
import { HoverTooltip } from 'design/Tooltip';
import FieldInput from 'shared/components/FieldInput';
import {
FieldSelect,
FieldSelectCreatable,
Expand Down Expand Up @@ -78,7 +80,8 @@ const AccessRule = memo(function AccessRule({
validation,
dispatch,
}: SectionPropsWithDispatch<RuleModel, AccessRuleValidationResult>) {
const { id, resources, verbs } = value;
const { id, resources, verbs, where } = value;
const theme = useTheme();
function setRule(rule: RuleModel) {
dispatch({ type: 'set-access-rule', payload: rule });
}
Expand Down Expand Up @@ -112,6 +115,27 @@ const AccessRule = memo(function AccessRule({
value={verbs}
onChange={v => setRule({ ...value, verbs: v })}
rule={precomputed(validation.fields.verbs)}
/>
<FieldInput
label="Filter"
toolTipContent={
<>
Optional condition that further limits the list of resources
affected by this rule, expressed using the{' '}
<Text
as="a"
href="https://goteleport.com/docs/reference/predicate-language/"
target="_blank"
color={theme.colors.interactive.solid.accent.default}
>
Teleport predicate language
</Text>
</>
}
tooltipSticky
disabled={isProcessing}
value={where}
onChange={e => setRule({ ...value, where: e.target.value })}
mb={0}
/>
</SectionBox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ describe('KubernetesAccessSection', () => {
await user.type(screen.getByPlaceholderText('label key'), 'some-key');
await user.type(screen.getByPlaceholderText('label value'), 'some-value');

await selectEvent.create(screen.getByLabelText('Users'), 'joe', {
createOptionText: 'User: joe',
});
await selectEvent.create(screen.getByLabelText('Users'), 'mary', {
createOptionText: 'User: mary',
});

await user.click(screen.getByRole('button', { name: 'Add a Resource' }));
expect(
reactSelectValueContainer(screen.getByLabelText('Kind'))
Expand Down Expand Up @@ -178,6 +185,10 @@ describe('KubernetesAccessSection', () => {
roleVersion: 'v7',
},
],
users: [
expect.objectContaining({ value: 'joe' }),
expect.objectContaining({ value: 'mary' }),
],
roleVersion: 'v7',
} as KubernetesAccess);
});
Expand Down Expand Up @@ -391,9 +402,12 @@ describe('DatabaseAccessSection', () => {

test('editing', async () => {
const { user, onChange } = setup();
await user.click(screen.getByRole('button', { name: 'Add a Label' }));
await user.type(screen.getByPlaceholderText('label key'), 'env');
await user.type(screen.getByPlaceholderText('label value'), 'prod');

const labels = within(screen.getByRole('group', { name: 'Labels' }));
await user.click(labels.getByRole('button', { name: 'Add a Label' }));
await user.type(labels.getByPlaceholderText('label key'), 'env');
await user.type(labels.getByPlaceholderText('label value'), 'prod');

await selectEvent.create(screen.getByLabelText('Database Names'), 'stuff', {
createOptionText: 'Database Name: stuff',
});
Expand All @@ -403,6 +417,16 @@ describe('DatabaseAccessSection', () => {
await selectEvent.create(screen.getByLabelText('Database Roles'), 'admin', {
createOptionText: 'Database Role: admin',
});

const dbServiceLabels = within(
screen.getByRole('group', { name: 'Database Service Labels' })
);
await user.click(
dbServiceLabels.getByRole('button', { name: 'Add a Label' })
);
await user.type(dbServiceLabels.getByPlaceholderText('label key'), 'foo');
await user.type(dbServiceLabels.getByPlaceholderText('label value'), 'bar');

expect(onChange).toHaveBeenLastCalledWith({
kind: 'db',
labels: [{ name: 'env', value: 'prod' }],
Expand All @@ -418,18 +442,29 @@ describe('DatabaseAccessSection', () => {
expect.objectContaining({ value: '{{internal.db_users}}' }),
expect.objectContaining({ label: 'mary', value: 'mary' }),
],
dbServiceLabels: [{ name: 'foo', value: 'bar' }],
} as DatabaseAccess);
});

test('validation', async () => {
const { user, validator } = setup();
await user.click(screen.getByRole('button', { name: 'Add a Label' }));
const labels = within(screen.getByRole('group', { name: 'Labels' }));
await user.click(labels.getByRole('button', { name: 'Add a Label' }));
const dbServiceLabelsGroup = within(
screen.getByRole('group', { name: 'Database Service Labels' })
);
await user.click(
dbServiceLabelsGroup.getByRole('button', { name: 'Add a Label' })
);
await selectEvent.create(screen.getByLabelText('Database Roles'), '*', {
createOptionText: 'Database Role: *',
});
act(() => validator.validate());
expect(
screen.getByPlaceholderText('label key')
labels.getByPlaceholderText('label key')
).toHaveAccessibleDescription('required');
expect(
dbServiceLabelsGroup.getByPlaceholderText('label key')
).toHaveAccessibleDescription('required');
expect(
screen.getByText('Wildcard is not allowed in database roles')
Expand Down
Loading
Loading