Skip to content

Commit

Permalink
Determine request mode from error returned from backend
Browse files Browse the repository at this point in the history
  • Loading branch information
kimlisa committed Oct 17, 2024
1 parent 508580a commit a7f49c2
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function KubeNamespaceSelector({
return (
<Box width="100%" mb={-3}>
<StyledSelect
label={`Namespaces ${namespaceRequired ? '(required)' : '(optional)'}:`}
label={`Namespaces ${namespaceRequired ? '(required)' : ''}:`}
inputId={kubeClusterItem.id}
width="100%"
placeholder="Start typing a namespace and press enter"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ const baseProps: RequestCheckoutWithSliderProps = {
{ value: 'namespace3', label: 'namespace3' },
{ value: 'namespace4', label: 'namespace4' },
],
allowedKubeSubresourceKinds: ['*'],
bulkToggleKubeResources: () => null,
createAttempt: { status: '' },
fetchResourceRequestRolesAttempt: { status: '' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,4 @@ const props: RequestCheckoutWithSliderProps = {
onStartTimeChange: () => null,
fetchKubeNamespaces: () => null,
bulkToggleKubeResources: () => null,
allowedKubeSubresourceKinds: [],
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
import { ArrowBack, ChevronDown, ChevronRight, Warning } from 'design/Icon';
import Table, { Cell } from 'design/DataTable';
import { Danger } from 'design/Alert';
import { KubeResourceKind } from 'teleport/services/kube';

import Validation, { useRule, Validator } from 'shared/components/Validation';
import { Attempt } from 'shared/hooks/useAttemptNext';
Expand All @@ -52,8 +51,8 @@ import { CreateRequest } from '../../Shared/types';
import { AssumeStartTime } from '../../AssumeStartTime/AssumeStartTime';
import { AccessDurationRequest } from '../../AccessDuration';
import {
checkForUnsupportedKubeRequestModes,
excludeKubeClusterWithNamespaces,
getKubeResourceRequestMode,
type KubeNamespaceRequest,
} from '../kube';

Expand Down Expand Up @@ -169,7 +168,6 @@ export function RequestCheckout<T extends PendingListItem>({
onStartTimeChange,
fetchKubeNamespaces,
bulkToggleKubeResources,
allowedKubeSubresourceKinds,
}: RequestCheckoutProps<T>) {
const [reason, setReason] = useState('');

Expand All @@ -192,14 +190,14 @@ export function RequestCheckout<T extends PendingListItem>({
}

const {
canRequestKubeCluster,
canRequestKubeResource,
canRequestKubeNamespace,
disableCheckoutFromKubeRestrictions,
} = getKubeResourceRequestMode(
allowedKubeSubresourceKinds,
!!data.find(d => d.kind === 'kube_cluster')
);
affectedKubeClusterName,
unsupportedKubeRequestModes,
requiresNamespaceSelect,
} = checkForUnsupportedKubeRequestModes(fetchResourceRequestRolesAttempt);

const hasUnsupportedKubeRequestModes = !!unsupportedKubeRequestModes;
const showRequestRoleErrBanner =
!hasUnsupportedKubeRequestModes && !requiresNamespaceSelect;

const isInvalidRoleSelection =
resourceRequestRoles.length > 0 &&
Expand All @@ -210,9 +208,14 @@ export function RequestCheckout<T extends PendingListItem>({
data.length === 0 ||
createAttempt.status === 'processing' ||
isInvalidRoleSelection ||
fetchResourceRequestRolesAttempt.status === 'failed' ||
fetchResourceRequestRolesAttempt.status === 'processing' ||
disableCheckoutFromKubeRestrictions;
(fetchResourceRequestRolesAttempt.status === 'failed' &&
hasUnsupportedKubeRequestModes) ||
requiresNamespaceSelect ||
fetchResourceRequestRolesAttempt.status === 'processing';

const cancelBtnDisabled =
createAttempt.status === 'processing' ||
fetchResourceRequestRolesAttempt.status === 'processing';

const numResourcesSelected = data.filter(item =>
excludeKubeClusterWithNamespaces(item, data)
Expand All @@ -239,10 +242,7 @@ export function RequestCheckout<T extends PendingListItem>({
};

function customRow(item: T) {
if (
item.kind === 'kube_cluster' &&
(canRequestKubeResource || canRequestKubeNamespace)
) {
if (item.kind === 'kube_cluster') {
return (
<td colSpan={3}>
<Flex>
Expand Down Expand Up @@ -270,7 +270,10 @@ export function RequestCheckout<T extends PendingListItem>({
toggleResource={toggleResource}
fetchKubeNamespaces={fetchKubeNamespaces}
bulkToggleKubeResources={bulkToggleKubeResources}
namespaceRequired={!canRequestKubeCluster}
namespaceRequired={
requiresNamespaceSelect &&
affectedKubeClusterName.includes(item.id)
}
/>
</Flex>
</Flex>
Expand All @@ -283,17 +286,19 @@ export function RequestCheckout<T extends PendingListItem>({
<Validation>
{({ validator }) => (
<>
{fetchResourceRequestRolesAttempt.status === 'failed' && (
<Alert
kind="danger"
children={fetchResourceRequestRolesAttempt.statusText}
/>
)}
{disableCheckoutFromKubeRestrictions && (
{showRequestRoleErrBanner &&
fetchResourceRequestRolesAttempt.status === 'failed' && (
<Alert
kind="danger"
children={fetchResourceRequestRolesAttempt.statusText}
/>
)}
{hasUnsupportedKubeRequestModes && (
<Alert kind="danger">
You can only request Kubernetes resource kind [
{allowedKubeSubresourceKinds.join(', ')}], but the web UI does not
support this kind yet. Use the{' '}
You can only request Kubernetes resource kind{' '}
{unsupportedKubeRequestModes} for cluster{' '}
{affectedKubeClusterName}, but the web UI does not support these
kinds yet. Use the{' '}
<ExternalLink
target="_blank"
href="https://goteleport.com/docs/admin-guides/access-controls/access-requests/resource-requests/#step-26-search-for-resources"
Expand Down Expand Up @@ -458,7 +463,7 @@ export function RequestCheckout<T extends PendingListItem>({
reset();
onClose();
}}
disabled={submitBtnDisabled}
disabled={cancelBtnDisabled}
>
Cancel
</ButtonSecondary>
Expand Down Expand Up @@ -904,7 +909,6 @@ export type RequestCheckoutProps<T extends PendingListItem = PendingListItem> =
kubeResources: PendingKubeResourceItem[],
kubeCluster: T
): void;
allowedKubeSubresourceKinds: KubeResourceKind[];
};

type SuccessComponentParams = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/

import { checkForUnsupportedKubeRequestModes } from './kube';

test('checkForUnsupportedKubeRequestModes: non failed status', () => {
const {
affectedKubeClusterName,
unsupportedKubeRequestModes,
requiresNamespaceSelect,
} = checkForUnsupportedKubeRequestModes({ status: '' });

expect(affectedKubeClusterName).toBeFalsy();
expect(unsupportedKubeRequestModes).toBeFalsy();
expect(requiresNamespaceSelect).toBeFalsy();
});

test('checkForUnsupportedKubeRequestModes: failed status with unsupported kinds', () => {
const {
affectedKubeClusterName,
unsupportedKubeRequestModes,
requiresNamespaceSelect,
} = checkForUnsupportedKubeRequestModes({
status: 'failed',
statusText: `Your Teleport roles request_mode field restricts you from requesting kinds [kube_cluster] for Kubernetes cluster pumpkin-kube-cluster. Allowed kinds: [pod secret]`,
});

expect(affectedKubeClusterName).toEqual(`pumpkin-kube-cluster`);
expect(unsupportedKubeRequestModes).toEqual('[pod secret]');
expect(requiresNamespaceSelect).toBeFalsy();
});

test('checkForUnsupportedKubeRequestModes: failed status with supported namespace', () => {
const {
affectedKubeClusterName,
unsupportedKubeRequestModes,
requiresNamespaceSelect,
} = checkForUnsupportedKubeRequestModes({
status: 'failed',
statusText: `Your Teleport roles request_mode field restricts you from requesting kinds [kube_cluster] for Kubernetes cluster pumpkin-kube-cluster. Allowed kinds: [pod secret namespace]`,
});

expect(affectedKubeClusterName).toEqual(`pumpkin-kube-cluster`);
expect(unsupportedKubeRequestModes).toBeFalsy();
expect(requiresNamespaceSelect).toBeTruthy();
});
58 changes: 36 additions & 22 deletions web/packages/shared/components/AccessRequests/NewRequest/kube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { KubeResourceKind } from 'teleport/services/kube';
import { Attempt } from 'shared/hooks/useAttemptNext';

import { PendingListItem } from './RequestCheckout';

Expand Down Expand Up @@ -48,33 +48,47 @@ export function excludeKubeClusterWithNamespaces(
}

/**
* Returns flags on what kind of kubernetes requests a user
* is allowed to make.
* Checks each data for kube_cluster or namespace
*/
export function getKubeResourceRequestMode(
allowedKubeSubresourceKinds: KubeResourceKind[],
hasSelectedKube
export function checkForUnsupportedKubeRequestModes(
requestRoleAttempt: Attempt
) {
const canRequestKubeCluster = allowedKubeSubresourceKinds.length === 0;
let unsupportedKubeRequestModes = '';
let affectedKubeClusterName = '';
let requiresNamespaceSelect = false;

const canRequestKubeResource =
allowedKubeSubresourceKinds.includes('*') || canRequestKubeCluster;
if (requestRoleAttempt.status === 'failed') {
const errMsg = requestRoleAttempt.statusText.toLowerCase();

const canRequestKubeNamespace =
canRequestKubeResource || allowedKubeSubresourceKinds.includes('namespace');
if (errMsg.includes('request_mode') && errMsg.includes('allowed kinds: ')) {
const allowedKinds = errMsg.split('allowed kinds: ')[1];

// This can happen if an admin restricts requests to a subresource
// kind that the web UI does not support yet.
const disableCheckoutFromKubeRestrictions =
hasSelectedKube &&
!canRequestKubeCluster &&
!canRequestKubeResource &&
!canRequestKubeNamespace;
// Web UI supports selecting namespace and wildcard
// which basically means requiring namespace.
if (allowedKinds.includes('*') || allowedKinds.includes('namespace')) {
requiresNamespaceSelect = true;
} else {
unsupportedKubeRequestModes = allowedKinds;
}

const initialSplit = errMsg.split('for kubernetes cluster');
if (initialSplit.length > 1) {
affectedKubeClusterName = initialSplit[1]
.split('. allowed kinds')[0]
.trim();
}

return {
affectedKubeClusterName,
requiresNamespaceSelect,
unsupportedKubeRequestModes,
};
}
}

return {
canRequestKubeCluster,
canRequestKubeResource,
canRequestKubeNamespace,
disableCheckoutFromKubeRestrictions,
affectedKubeClusterName,
unsupportedKubeRequestModes,
requiresNamespaceSelect,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ export function AccessRequestCheckout() {
// that will be merged right after this one (once both are approved)
bulkToggleKubeResources={() => null}
fetchKubeNamespaces={() => null}
allowedKubeSubresourceKinds={[]}
/>
)}
</Transition>
Expand Down

0 comments on commit a7f49c2

Please sign in to comment.