diff --git a/web/packages/shared/components/ToolTip/ToolTip.tsx b/web/packages/shared/components/ToolTip/ToolTip.tsx
index 4b27754a06b9c..9b02ae9ab511f 100644
--- a/web/packages/shared/components/ToolTip/ToolTip.tsx
+++ b/web/packages/shared/components/ToolTip/ToolTip.tsx
@@ -28,6 +28,7 @@ export const ToolTipInfo: React.FC<
muteIconColor?: boolean;
sticky?: boolean;
maxWidth?: number;
+ kind?: 'info' | 'warning';
}>
> = ({
children,
@@ -35,6 +36,7 @@ export const ToolTipInfo: React.FC<
muteIconColor,
sticky = false,
maxWidth = 350,
+ kind = 'info',
}) => {
const [anchorEl, setAnchorEl] = useState();
const open = Boolean(anchorEl);
@@ -71,7 +73,12 @@ export const ToolTipInfo: React.FC<
height: 18px;
`}
>
-
+ {kind === 'info' && (
+
+ )}
+ {kind === 'warning' && (
+
+ )}
@@ -108,3 +115,12 @@ const InfoIcon = styled(Icons.Info)<{ $muteIconColor?: boolean }>`
width: 18px;
color: ${p => (p.$muteIconColor ? p.theme.colors.text.disabled : 'inherit')};
`;
+
+const WarningIcon = styled(Icons.Warning)<{ $muteIconColor?: boolean }>`
+ height: 18px;
+ width: 18px;
+ color: ${p =>
+ p.$muteIconColor
+ ? p.theme.colors.text.disabled
+ : p.theme.colors.interactive.solid.alert.default.background};
+`;
diff --git a/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.test.tsx b/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.test.tsx
index ed6151770e7cb..0dc8135e747ef 100644
--- a/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.test.tsx
+++ b/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.test.tsx
@@ -26,7 +26,7 @@ import {
import { EditAwsOidcIntegrationDialog } from './EditAwsOidcIntegrationDialog';
-test('user acknowledging script was ran when s3 bucket fields are edited', async () => {
+test('user acknowledging script was ran when reconfiguring', async () => {
render(
null}
@@ -37,8 +37,6 @@ test('user acknowledging script was ran when s3 bucket fields are edited', async
name: 'some-integration-name',
spec: {
roleArn: 'arn:aws:iam::123456789012:role/johndoe',
- issuerS3Bucket: 'test-value',
- issuerS3Prefix: '',
},
statusCode: IntegrationStatusCode.Running,
}}
@@ -49,31 +47,32 @@ test('user acknowledging script was ran when s3 bucket fields are edited', async
expect(screen.queryByTestId('scriptbox')).not.toBeInTheDocument();
expect(screen.queryByLabelText(/I ran the command/i)).not.toBeInTheDocument();
expect(
- screen.queryByRole('button', { name: /generate command/i })
+ screen.queryByRole('button', { name: /reconfigure/i })
).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
- // Fill in the s3 prefix field.
- fireEvent.change(screen.getByPlaceholderText(/prefix/i), {
- target: { value: 'test-value' },
+ // Check s3 related fields are not rendered.
+ expect(screen.queryByText(/not recommended/)).not.toBeInTheDocument();
+ expect(screen.queryByText('Amazon S3')).not.toBeInTheDocument();
+
+ // change role arn
+ fireEvent.change(screen.getByPlaceholderText(/arn:aws:iam:/i), {
+ target: { value: 'arn:aws:iam::123456789011:role/other' },
});
+
await waitFor(() =>
- expect(
- screen.getByRole('button', { name: /generate command/i })
- ).toBeEnabled()
+ expect(screen.getByRole('button', { name: /reconfigure/i })).toBeEnabled()
);
- // When clicking on generate command:
+ // When clicking on reconfigure:
// - script rendered
// - checkbox to confirm user has ran command
- // - edit button replaces generate command button
+ // - edit button replaces reconfigure button
// - save button still disabled
- await userEvent.click(
- screen.getByRole('button', { name: /generate command/i })
- );
+ await userEvent.click(screen.getByRole('button', { name: /reconfigure/i }));
await screen.findByRole('button', { name: /edit/i });
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
expect(
- screen.queryByRole('button', { name: /generate command/i })
+ screen.queryByRole('button', { name: /reconfigure/i })
).not.toBeInTheDocument();
expect(screen.getByLabelText(/I ran the command/i)).toBeInTheDocument();
expect(screen.getByTestId('scriptbox')).toBeInTheDocument();
@@ -91,16 +90,14 @@ test('user acknowledging script was ran when s3 bucket fields are edited', async
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled()
);
- // Click on edit, should replace it with generate command
+ // Click on edit, should replace it with reconfigure
await userEvent.click(screen.getByRole('button', { name: /edit/i }));
await waitFor(() =>
- expect(
- screen.getByRole('button', { name: /generate command/i })
- ).toBeEnabled()
+ expect(screen.getByRole('button', { name: /reconfigure/i })).toBeEnabled()
);
});
-test('render warning on save when leaving s3 fields empty', async () => {
+test('render warning when s3 buckets are present', async () => {
const edit = jest.fn(() => Promise.resolve());
render(
{
name: 'some-integration-name',
spec: {
roleArn: 'arn:aws:iam::123456789012:role/johndoe',
- issuerS3Bucket: '',
- issuerS3Prefix: '',
+ issuerS3Bucket: 'some-bucket',
+ issuerS3Prefix: 'some-prefix',
},
statusCode: IntegrationStatusCode.Running,
}}
@@ -124,112 +121,25 @@ test('render warning on save when leaving s3 fields empty', async () => {
expect(screen.queryByTestId('scriptbox')).not.toBeInTheDocument();
expect(screen.queryByLabelText(/I ran the command/i)).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
- expect(
- screen.queryByRole('button', { name: /generate command/i })
- ).not.toBeInTheDocument();
-
- // Enable the generate command button by changing a field.
- fireEvent.change(screen.getByPlaceholderText(/arn:aws:iam:/i), {
- target: { value: 'arn:aws:iam::123456789012:role/someonelse' },
- });
- await waitFor(() =>
- expect(
- screen.getByRole('button', { name: /generate command/i })
- ).toBeEnabled()
- );
-
- expect(screen.queryByLabelText(/I ran the command/i)).not.toBeInTheDocument();
- expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
-
- await userEvent.click(
- screen.getByRole('button', { name: /generate command/i })
- );
- await screen.findByRole('button', { name: /edit/i });
- expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
-
- await userEvent.click(screen.getByLabelText(/I ran the command/i));
- await waitFor(() =>
- expect(screen.getByRole('button', { name: /save/i })).toBeEnabled()
- );
-
- // Clicking on save without defining s3 fields, should render
- // a warning.
- await userEvent.click(screen.getByRole('button', { name: /save/i }));
- await screen.findByText(/recommended to use an S3 bucket/i);
- expect(edit).not.toHaveBeenCalled();
-
- // Canceling and saving should re-render the warning.
- await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
- await screen.findByRole('button', { name: /save/i });
-
- await userEvent.click(screen.getByRole('button', { name: /save/i }));
- await screen.findByText(/recommended to use an S3 bucket/i);
-
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- await waitFor(() => expect(edit).toHaveBeenCalledTimes(1));
-});
-
-test('render warning on save when deleting existing s3 fields', async () => {
- const edit = jest.fn(() => Promise.resolve());
- render(
- null}
- edit={edit}
- integration={{
- resourceType: 'integration',
- kind: IntegrationKind.AwsOidc,
- name: 'some-integration-name',
- spec: {
- roleArn: 'arn:aws:iam::123456789012:role/johndoe',
- issuerS3Bucket: 'delete-me',
- issuerS3Prefix: 'delete-me',
- },
- statusCode: IntegrationStatusCode.Running,
- }}
- />
- );
+ // Check s3 related fields/warnings are rendered.
expect(
- screen.queryByRole('button', { name: /generate command/i })
- ).not.toBeInTheDocument();
-
- // Delete the s3 fields.
- fireEvent.change(screen.getByPlaceholderText(/bucket/i), {
- target: { value: '' },
- });
- fireEvent.change(screen.getByPlaceholderText(/prefix/i), {
- target: { value: '' },
- });
- await waitFor(() =>
- expect(
- screen.getByRole('button', { name: /generate command/i })
- ).toBeEnabled()
- );
-
- expect(screen.queryByLabelText(/I ran the command/i)).not.toBeInTheDocument();
- expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
+ screen.getByRole('button', { name: /reconfigure/i })
+ ).toBeInTheDocument();
+ expect(screen.getByText(/not recommended/)).toBeInTheDocument();
+ expect(screen.getByText(/Amazon S3 Location/)).toBeInTheDocument();
- await userEvent.click(
- screen.getByRole('button', { name: /generate command/i })
- );
- await screen.findByRole('button', { name: /edit/i });
- expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
+ // Clicking on reconfigure should hide s3 fields.
+ await userEvent.click(screen.getByRole('button', { name: /reconfigure/i }));
+ await screen.findByText(/AWS CloudShell/);
+ expect(screen.queryByText(/not recommended/)).not.toBeInTheDocument();
+ expect(screen.queryByText('/Amazon S3 Location/')).not.toBeInTheDocument();
- await userEvent.click(screen.getByLabelText(/I ran the command/i));
- await waitFor(() =>
- expect(screen.getByRole('button', { name: /save/i })).toBeEnabled()
- );
-
- // Test for warning render.
- await userEvent.click(screen.getByRole('button', { name: /save/i }));
- await screen.findByText(/recommended to use an S3 bucket/i);
- expect(edit).not.toHaveBeenCalled();
- expect(
- screen.getByText(/recommended to use an S3 bucket/i)
- ).toBeInTheDocument();
+ // Clicking on edit, should render it back.
+ await userEvent.click(screen.getByRole('button', { name: /edit/i }));
- await userEvent.click(screen.getByRole('button', { name: /continue/i }));
- await waitFor(() => expect(edit).toHaveBeenCalledTimes(1));
+ await screen.findByText(/not recommended/);
+ await screen.findByText(/Amazon S3 Location/);
});
test('edit invalid fields', async () => {
@@ -249,14 +159,10 @@ test('edit invalid fields', async () => {
});
await waitFor(() =>
- expect(
- screen.getByRole('button', { name: /generate command/i })
- ).toBeEnabled()
+ expect(screen.getByRole('button', { name: /reconfigure/i })).toBeEnabled()
);
- await userEvent.click(
- screen.getByRole('button', { name: /generate command/i })
- );
+ await userEvent.click(screen.getByRole('button', { name: /reconfigure/i }));
await screen.findByText(/invalid role ARN format/i);
});
@@ -286,14 +192,10 @@ test('edit submit called with proper fields', async () => {
});
await waitFor(() =>
- expect(
- screen.getByRole('button', { name: /generate command/i })
- ).toBeEnabled()
+ expect(screen.getByRole('button', { name: /reconfigure/i })).toBeEnabled()
);
- await userEvent.click(
- screen.getByRole('button', { name: /generate command/i })
- );
+ await userEvent.click(screen.getByRole('button', { name: /reconfigure/i }));
await screen.findByRole('button', { name: /edit/i });
await userEvent.click(screen.getByLabelText(/I ran the command/i));
@@ -305,8 +207,6 @@ test('edit submit called with proper fields', async () => {
expect(mockEditFn).toHaveBeenCalledWith({
roleArn: 'arn:aws:iam::123456789011:role/other',
- s3Bucket: 'other-bucket',
- s3Prefix: 'other-prefix',
});
});
diff --git a/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.tsx b/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.tsx
index 35a3dfd1c0dec..3813f7381ef49 100644
--- a/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.tsx
+++ b/web/packages/teleport/src/Integrations/EditAwsOidcIntegrationDialog.tsx
@@ -33,6 +33,7 @@ import Dialog, {
DialogContent,
DialogFooter,
} from 'design/DialogConfirmation';
+import { OutlineInfo, OutlineWarn } from 'design/Alert/Alert';
import useAttempt from 'shared/hooks/useAttemptNext';
import FieldInput from 'shared/components/FieldInput';
import Validation, { Validator } from 'shared/components/Validation';
@@ -47,7 +48,6 @@ import { splitAwsIamArn } from 'teleport/services/integrations/aws';
import { EditableIntegrationFields } from './Operations/useIntegrationOperation';
import { S3BucketConfiguration } from './Enroll/AwsOidc/S3BucketConfiguration';
-import { S3BucketWarningBanner } from './Enroll/AwsOidc/S3BucketWarningBanner';
type Props = {
close(): void;
@@ -59,15 +59,7 @@ export function EditAwsOidcIntegrationDialog(props: Props) {
const { close, edit, integration } = props;
const { attempt, run } = useAttempt();
- const [showS3BucketWarning, setShowS3BucketWarning] = useState(false);
const [roleArn, setRoleArn] = useState(integration.spec.roleArn);
- const [s3Bucket, setS3Bucket] = useState(
- () => integration.spec.issuerS3Bucket
- );
- const [s3Prefix, setS3Prefix] = useState(
- () => integration.spec.issuerS3Prefix
- );
-
const [scriptUrl, setScriptUrl] = useState('');
const [confirmed, setConfirmed] = useState(false);
@@ -76,7 +68,7 @@ export function EditAwsOidcIntegrationDialog(props: Props) {
return;
}
- run(() => edit({ roleArn, s3Bucket, s3Prefix }));
+ run(() => edit({ roleArn }));
}
function generateAwsOidcConfigIdpScript(validator: Validator) {
@@ -92,24 +84,20 @@ export function EditAwsOidcIntegrationDialog(props: Props) {
const newScriptUrl = cfg.getAwsOidcConfigureIdpScriptUrl({
integrationName: integration.name,
roleName: arnResourceName,
- s3Bucket: s3Bucket,
- s3Prefix: s3Prefix,
});
setScriptUrl(newScriptUrl);
}
+ const s3Bucket = integration.spec.issuerS3Bucket;
+ const s3Prefix = integration.spec.issuerS3Prefix;
+ const showReadonlyS3Fields = s3Bucket || s3Prefix;
+
const isProcessing = attempt.status === 'processing';
- const requiresS3BucketWarning = !s3Bucket && !s3Prefix;
const showGenerateCommand =
- integration.spec.issuerS3Bucket !== s3Bucket ||
- integration.spec.issuerS3Prefix !== s3Prefix ||
- integration.spec.roleArn !== roleArn;
+ integration.spec.roleArn !== roleArn || showReadonlyS3Fields;
- const changeDetected =
- integration.spec.issuerS3Bucket !== s3Bucket ||
- integration.spec.issuerS3Prefix !== s3Prefix ||
- integration.spec.roleArn !== roleArn;
+ const changeDetected = integration.spec.roleArn !== roleArn;
return (
@@ -135,7 +123,7 @@ export function EditAwsOidcIntegrationDialog(props: Props) {
value={integration.name}
readonly={true}
/>
-
+
-
+ {showReadonlyS3Fields && !scriptUrl && (
+ <>
+
+
+ Using an S3 bucket to store the OpenID Configuration is not
+ recommended. Reconfiguring this integration is suggested
+ (this will not break existing features).
+
+ >
+ )}
{scriptUrl && (
- Configure the required permission in your AWS account.
Open{' '}
+ {showReadonlyS3Fields && (
+
+
+ After running the command, delete the previous{' '}
+
+ identity provider
+ {' '}
+ along with its{' '}
+
+ S3 bucket
+ {' '}
+ from your AWS console.
+
+
+ )}
)}
@@ -192,16 +206,13 @@ export function EditAwsOidcIntegrationDialog(props: Props) {
Edit
)}
- {showGenerateCommand && !scriptUrl && (
+ {!scriptUrl && showGenerateCommand && (
generateAwsOidcConfigIdpScript(validator)}
- disabled={
- (!requiresS3BucketWarning && (!s3Bucket || !s3Prefix)) ||
- !roleArn
- }
+ disabled={!roleArn}
>
- Generate Command
+ Reconfigure
)}
@@ -217,40 +228,20 @@ export function EditAwsOidcIntegrationDialog(props: Props) {
}}
/>
)}
-
- {requiresS3BucketWarning && showS3BucketWarning ? (
- setShowS3BucketWarning(false)}
- onContinue={() => {
- setShowS3BucketWarning(false);
- handleEdit(validator);
- }}
- btnFlexWrap={true}
- />
- ) : (
- <>
- {
- if (requiresS3BucketWarning) {
- setShowS3BucketWarning(true);
- } else {
- handleEdit(validator);
- }
- }}
- >
- Save
-
-
- Cancel
-
- >
- )}
+ handleEdit(validator)}
+ >
+ Save
+
+
+ Cancel
+
)}
@@ -263,3 +254,17 @@ const EditableBox = styled(Box)`
border: 2px solid ${p => p.theme.colors.spotBackground[1]};
background-color: ${p => p.theme.colors.spotBackground[0]};
`;
+
+function getIdpArn({
+ s3Bucket,
+ s3Prefix,
+ roleArn,
+}: {
+ s3Bucket: string;
+ s3Prefix: string;
+ roleArn: string;
+}) {
+ const { awsAccountId } = splitAwsIamArn(roleArn);
+ const arn = `arn:aws:iam::${awsAccountId}:oidc-provider/${s3Bucket}.s3.amazonaws.com/${s3Prefix}`;
+ return encodeURIComponent(arn);
+}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.story.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.story.tsx
index c7bf533f841b5..a00a102207ddd 100644
--- a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.story.tsx
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.story.tsx
@@ -20,26 +20,9 @@ import React from 'react';
import { MemoryRouter } from 'react-router';
import { AwsOidc } from './AwsOidc';
-import { S3BucketWarningBanner } from './S3BucketWarningBanner';
-
-export default {
- title: 'Teleport/Integrations/Enroll/AwsOidc',
-};
export const Flow = () => (
);
-
-export const SBucketWarning = () => (
- null} onContinue={() => null} />
-);
-
-export const SBucketWarningWithReview = () => (
- null}
- onContinue={() => null}
- reviewing={true}
- />
-);
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx
index 79d7dac8e0b45..4415fb646d5b2 100644
--- a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/AwsOidc.tsx
@@ -20,15 +20,7 @@ import React, { useEffect, useState } from 'react';
import { Link as InternalRouteLink } from 'react-router-dom';
import { useLocation } from 'react-router';
import styled from 'styled-components';
-import {
- Box,
- ButtonSecondary,
- Text,
- Link,
- Flex,
- ButtonPrimary,
- ButtonText,
-} from 'design';
+import { Box, ButtonSecondary, Text, Link, Flex, ButtonPrimary } from 'design';
import * as Icons from 'design/Icon';
import FieldInput from 'shared/components/FieldInput';
import { requiredIamRoleName } from 'shared/components/Validation/rules';
@@ -53,24 +45,12 @@ import {
import cfg from 'teleport/config';
import { FinishDialog } from './FinishDialog';
-import { S3BucketConfiguration } from './S3BucketConfiguration';
-import {
- getDefaultS3BucketName,
- requiredPrefixName,
- validPrefixNameToolTipContent,
-} from './Shared/utils';
-import { S3BucketWarningBanner } from './S3BucketWarningBanner';
export function AwsOidc() {
const [integrationName, setIntegrationName] = useState('');
const [roleArn, setRoleArn] = useState('');
const [roleName, setRoleName] = useState('');
const [scriptUrl, setScriptUrl] = useState('');
- const [s3Bucket, setS3Bucket] = useState(() => getDefaultS3BucketName());
- const [s3Prefix, setS3Prefix] = useState('');
- const [showS3BucketWarning, setShowS3BucketWarning] = useState(false);
- const [confirmedS3BucketWarning, setConfirmedS3BucketWarning] =
- useState(false);
const [createdIntegration, setCreatedIntegration] = useState();
const { attempt, run } = useAttempt('');
@@ -81,8 +61,6 @@ export function AwsOidc() {
kind: IntegrationEnrollKind.AwsOidc,
});
- const requiresS3BucketWarning = !s3Bucket && !s3Prefix;
-
useEffect(() => {
// If a user came from the discover wizard,
// discover wizard will send of appropriate events.
@@ -107,8 +85,6 @@ export function AwsOidc() {
subKind: IntegrationKind.AwsOidc,
awsoidc: {
roleArn,
- issuerS3Bucket: s3Bucket,
- issuerS3Prefix: s3Prefix,
},
})
.then(res => {
@@ -139,8 +115,6 @@ export function AwsOidc() {
const newScriptUrl = cfg.getAwsOidcConfigureIdpScriptUrl({
integrationName,
roleName,
- s3Bucket,
- s3Prefix,
});
setScriptUrl(newScriptUrl);
@@ -182,23 +156,12 @@ export function AwsOidc() {
Step 1
setIntegrationName(e.target.value)}
disabled={!!scriptUrl}
- onBlur={() => {
- // s3Bucket by default is defined.
- // If empty user intentionally cleared it.
- if (!integrationName || (!s3Bucket && !s3Prefix)) return;
- // Help come up with a default prefix name for user.
- if (!s3Prefix) {
- setS3Prefix(`${integrationName}-oidc-idp`);
- }
- }}
- toolTipContent={validPrefixNameToolTipContent('Integration')}
/>
setRoleName(e.target.value)}
disabled={!!scriptUrl}
/>
-
- {confirmedS3BucketWarning && (
-
- setShowS3BucketWarning(true)}
- alignItems="center"
- >
-
- Click to view S3 Bucket Warning
-
-
- )}
- {showS3BucketWarning ? (
- setShowS3BucketWarning(false)}
- onContinue={() => {
- setShowS3BucketWarning(false);
- setConfirmedS3BucketWarning(true);
- generateAwsOidcConfigIdpScript(validator);
- }}
- reviewing={confirmedS3BucketWarning}
- />
- ) : scriptUrl ? (
+ {scriptUrl ? (
{
setScriptUrl('');
- setConfirmedS3BucketWarning(false);
}}
>
Edit
@@ -252,13 +184,7 @@ export function AwsOidc() {
) : (
{
- if (requiresS3BucketWarning) {
- setShowS3BucketWarning(true);
- } else {
- generateAwsOidcConfigIdpScript(validator);
- }
- }}
+ onClick={() => generateAwsOidcConfigIdpScript(validator)}
>
Generate Command
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/S3BucketConfiguration.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/S3BucketConfiguration.tsx
index 874564aa1a4f6..a225196d65dfc 100644
--- a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/S3BucketConfiguration.tsx
+++ b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/S3BucketConfiguration.tsx
@@ -21,62 +21,37 @@ import { Text, Flex } from 'design';
import FieldInput from 'shared/components/FieldInput';
import { ToolTipInfo } from 'shared/components/ToolTip';
-import {
- requiredBucketName,
- requiredPrefixName,
- validPrefixNameToolTipContent,
-} from './Shared/utils';
-
export function S3BucketConfiguration({
s3Bucket,
- setS3Bucket,
s3Prefix,
- setS3Prefix,
- disabled,
}: {
s3Bucket: string;
- setS3Bucket(s: string): void;
s3Prefix: string;
- setS3Prefix(s: string): void;
- disabled: boolean;
}) {
return (
<>
Amazon S3 Location
-
- Teleport will create and use Amazon S3 Bucket as this integration's
- issuer and will publicly host two files: one for the OpenID
- configuration and another one for the public key.
+
+ Deprecated. Amazon is now validating the IdP certificate against a
+ list of root CAs. Storing the OpenID Configuration in S3 is no longer
+ required, and should be removed to improve security.
setS3Bucket(e.target.value.trim())}
- disabled={disabled}
- toolTipContent={
-
- Bucket name can consist only of lowercase letters and numbers.
- Hyphens (-) are allowed in between letters and numbers. An
- existing bucket might be used, but it must have Access Control
- Lists (ACL) enabled.
-
- }
+ readonly={true}
/>
setS3Prefix(e.target.value.trim())}
- disabled={disabled}
- toolTipContent={validPrefixNameToolTipContent('Prefix')}
+ readonly={true}
/>
>
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/S3BucketWarningBanner.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/S3BucketWarningBanner.tsx
deleted file mode 100644
index e90ef71b4cf7d..0000000000000
--- a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/S3BucketWarningBanner.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 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 { ButtonText, Box, Text, Flex, ButtonBorder } from 'design';
-import { OutlineWarn } from 'design/Alert/Alert';
-import { Notification } from 'design/Icon';
-import styled from 'styled-components';
-
-export const S3BucketWarningBanner = ({
- onClose,
- onContinue,
- reviewing = false,
- btnFlexWrap = false,
-}: {
- onClose(): void;
- onContinue(): void;
- reviewing?: boolean;
- btnFlexWrap?: boolean;
-}) => {
- return (
-
-
-
-
-
-
-
- It is recommended to use an S3 bucket to host the public keys.
-
-
- Without an S3 bucket, you will be required to append the new
- certificate's thumbprint in the AWS IAM/Identity Provider section
- after you have renewed and started using the new certificate.
-
-
-
- {reviewing ? (
-
- Ok
-
- ) : (
- <>
- Continue
- Cancel
- >
- )}
-
-
-
- );
-};
-
-const BellIcon = styled(Notification)`
- background-color: ${p => p.theme.colors.warning.hover};
- border-radius: 100px;
- height: 32px;
- width: 32px;
- color: ${p => p.theme.colors.text.primaryInverse};
- margin-right: ${p => p.theme.space[3]}px;
-`;
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/Shared.tsx b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/Shared.tsx
deleted file mode 100644
index 82f059ddbc15b..0000000000000
--- a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/Shared.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * 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 { Validator } from 'shared/components/Validation';
-
-import cfg, { UrlAwsOidcConfigureIdp } from 'teleport/config';
-
-export function generateAwsOidcConfigIdpScript(
- validator: Validator,
- setScriptUrl: (s: string) => void,
- params: UrlAwsOidcConfigureIdp
-) {
- if (!validator.validate()) {
- return;
- }
-
- validator.reset();
-
- const newScriptUrl = cfg.getAwsOidcConfigureIdpScriptUrl(params);
-
- setScriptUrl(newScriptUrl);
-}
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/utils.test.ts b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/utils.test.ts
deleted file mode 100644
index f65e9294cc6bc..0000000000000
--- a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/utils.test.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * 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 cfg from 'teleport/config';
-
-import {
- getDefaultS3BucketName,
- getDefaultS3PrefixName,
- requiredPrefixName,
- requiredBucketName,
-} from './utils';
-
-const defaultProxyCluster = cfg.proxyCluster;
-
-afterEach(() => {
- cfg.proxyCluster = defaultProxyCluster;
-});
-
-test('getDefaultS3BucketName', () => {
- cfg.proxyCluster = 'llama#42.slkej';
- expect(getDefaultS3BucketName()).toBe('');
-
- cfg.proxyCluster = 'llama.cloud.gravitational-io';
- expect(getDefaultS3BucketName()).toBe('llama-cloud-gravitational-io');
-});
-
-test('getDefaultS3PrefixName', () => {
- expect(getDefaultS3PrefixName('')).toBe('');
- expect(getDefaultS3PrefixName('sdf@$@#sdf')).toBe('');
-
- cfg.proxyCluster = 'llama.cloud.gravitational-io';
- expect(getDefaultS3PrefixName('int-name')).toBe('int-name-oidc-idp');
-});
-
-describe('requiredPrefixName', () => {
- const requiredField = true;
- test.each`
- input | valid
- ${''} | ${false}
- ${Array.from('x'.repeat(64))} | ${false}
- ${'-sdf'} | ${false}
- ${'sdfs-'} | ${false}
- ${'_sdf'} | ${false}
- ${'sdfd_'} | ${false}
- ${'..sdf'} | ${false}
- ${'sdf.'} | ${false}
- ${'sdlfkjs/dfsd'} | ${false}
- ${'Asd09f-_.sdfDFs1'} | ${true}
- `('validity of input($input) should be ($valid)', ({ input, valid }) => {
- const result = requiredPrefixName(requiredField)(input)();
- expect(result.valid).toEqual(valid);
- });
-
- test('empty prefix name is valid if not a required field', () => {
- const requiredField = false;
- expect(requiredPrefixName(requiredField)('')().valid).toBeTruthy();
- });
-});
-
-describe('requiredBucketName', () => {
- test.each`
- input | valid
- ${''} | ${false}
- ${Array.from('x'.repeat(64))} | ${false}
- ${Array.from('x'.repeat(2))} | ${false}
- ${'-sdf'} | ${false}
- ${'sdfs-'} | ${false}
- ${'sdfds_sdf'} | ${false}
- ${'xn--sdf'} | ${false}
- ${'sthree-sdf'} | ${false}
- ${'sthree-configurator-dfs'} | ${false}
- ${'sdf-s3alias'} | ${false}
- ${'sdf--ol-s3'} | ${false}
- ${'Asd09f-sdfDFs1'} | ${false}
- ${'sdf0-dfs0'} | ${true}
- `('validity of input($input) should be ($valid)', ({ input, valid }) => {
- const requiredField = true;
- const result = requiredBucketName(requiredField)(input)();
- expect(result.valid).toEqual(valid);
- });
-
- test('empty bucket name is valid if not a required field', () => {
- const requiredField = false;
- expect(requiredBucketName(requiredField)('')().valid).toBeTruthy();
- });
-});
diff --git a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/utils.ts b/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/utils.ts
deleted file mode 100644
index 28e8e20a0fac1..0000000000000
--- a/web/packages/teleport/src/Integrations/Enroll/AwsOidc/Shared/utils.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-/**
- * 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 { Rule } from 'shared/components/Validation/rules';
-
-import cfg from 'teleport/config';
-
-// Must start and end with lowercase letters or numbers.
-// Can have hyphens in between start and end.
-const bucketNameRegex = new RegExp(/^[a-z0-9][a-z0-9-]*[a-z0-9]$/);
-export const requiredBucketName =
- (required): Rule =>
- inputVal =>
- () => {
- if (!inputVal) {
- return {
- valid: !required,
- message: required ? 'required' : '',
- };
- }
-
- if (inputVal.length < 3 || inputVal.length > 63) {
- return {
- valid: false,
- message: 'name should be 3-63 characters',
- };
- }
-
- if (!bucketNameRegex.test(inputVal)) {
- return {
- valid: false,
- message: 'name is in a invalid format',
- };
- }
-
- if (inputVal.startsWith('xn--')) {
- return {
- valid: false,
- message: 'cannot start with "xn--"',
- };
- }
-
- if (inputVal.startsWith('sthree-')) {
- return {
- valid: false,
- message: 'cannot start with "sthree-"',
- };
- }
-
- if (inputVal.startsWith('sthree-configurator')) {
- return {
- valid: false,
- message: 'cannot start with "sthree-configurator"',
- };
- }
-
- if (inputVal.endsWith('-s3alias')) {
- return {
- valid: false,
- message: 'cannot end with "-s3alias"',
- };
- }
-
- if (inputVal.endsWith('--ol-s3')) {
- return {
- valid: false,
- message: 'cannot end with "--ol-s3"',
- };
- }
-
- return {
- valid: true,
- };
- };
-
-// Must start and end with letters or numbers.
-// Can have hyphens, underscores, and periods in between start and end.
-const prefixNameRegex = new RegExp(/^[a-zA-Z0-9][a-zA-Z0-9-_.]*[a-zA-Z0-9]$/);
-export const requiredPrefixName =
- (required): Rule =>
- inputVal =>
- () => {
- if (!inputVal) {
- return {
- valid: !required,
- message: required ? 'required' : '',
- };
- }
-
- // Just a random hard cap.
- if (inputVal.length > 63) {
- return {
- valid: false,
- message: 'name can be max 63 characters long',
- };
- }
-
- if (!prefixNameRegex.test(inputVal)) {
- return {
- valid: false,
- message: 'name is in a invalid format',
- };
- }
-
- return {
- valid: true,
- };
- };
-
-export function getDefaultS3BucketName() {
- const modifiedClusterName = cfg.proxyCluster.replaceAll('.', '-');
- if (bucketNameRegex.test(modifiedClusterName)) {
- return modifiedClusterName;
- }
-
- return '';
-}
-
-export function getDefaultS3PrefixName(integrationName: string) {
- if (!integrationName || !prefixNameRegex.test(integrationName)) {
- return '';
- }
-
- return `${integrationName}-oidc-idp`;
-}
-
-export function validPrefixNameToolTipContent(fieldName: string) {
- return `${fieldName} name can consist only of letters and numbers. \
- Hyphens (-), dots (.), and underscores (_) are allowed in between letters and numbers.`;
-}
diff --git a/web/packages/teleport/src/Integrations/IntegrationList.test.tsx b/web/packages/teleport/src/Integrations/IntegrationList.test.tsx
deleted file mode 100644
index 5dce1329ae977..0000000000000
--- a/web/packages/teleport/src/Integrations/IntegrationList.test.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * 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 { render, screen, userEvent } from 'design/utils/testing';
-
-import {
- IntegrationKind,
- integrationService,
- IntegrationStatusCode,
-} from 'teleport/services/integrations';
-
-import { IntegrationList } from './IntegrationList';
-
-test('aws oidc row without s3 fields should render tooltip', async () => {
- jest
- .spyOn(integrationService, 'fetchThumbprint')
- .mockResolvedValue('some-thumbprint');
-
- render(
-
- );
-
- expect(screen.getByText('aws')).toBeInTheDocument();
- expect(screen.getByText(/running/i)).toBeInTheDocument();
- await userEvent.hover(screen.getByRole('icon'));
- expect(screen.queryByTestId('btn-copy')).not.toBeInTheDocument();
-
- await userEvent.click(screen.getByText(/generate a new thumbprint/i));
- expect(screen.getByText(/some-thumbprint/i)).toBeInTheDocument();
-});
-
-test('aws oidc row with s3 fields should NOT render tooltip', async () => {
- jest
- .spyOn(integrationService, 'fetchThumbprint')
- .mockResolvedValue('some-thumbprint');
-
- render(
-
- );
-
- expect(screen.getByText('aws')).toBeInTheDocument();
- expect(screen.getByText(/running/i)).toBeInTheDocument();
- expect(screen.queryByRole('icon')).not.toBeInTheDocument();
-});
diff --git a/web/packages/teleport/src/Integrations/IntegrationList.tsx b/web/packages/teleport/src/Integrations/IntegrationList.tsx
index cfe00ae717398..a7a41ccbbafa9 100644
--- a/web/packages/teleport/src/Integrations/IntegrationList.tsx
+++ b/web/packages/teleport/src/Integrations/IntegrationList.tsx
@@ -51,7 +51,6 @@ import {
import cfg from 'teleport/config';
import { ExternalAuditStorageOpType } from './Operations/useIntegrationOperation';
-import { UpdateAwsOidcThumbprint } from './UpdateAwsOidcThumbprint';
type Props = {
list: IntegrationLike[];
@@ -197,9 +196,6 @@ const StatusCell = ({ item }: { item: IntegrationLike }) => {
{getStatusCodeTitle(item.statusCode)}
-
-
-
);
diff --git a/web/packages/teleport/src/Integrations/Integrations.story.tsx b/web/packages/teleport/src/Integrations/Integrations.story.tsx
index c47261b869aef..aa6c697ec525c 100644
--- a/web/packages/teleport/src/Integrations/Integrations.story.tsx
+++ b/web/packages/teleport/src/Integrations/Integrations.story.tsx
@@ -26,7 +26,6 @@ import {
import { IntegrationList } from './IntegrationList';
import { DeleteIntegrationDialog } from './RemoveIntegrationDialog';
import { EditAwsOidcIntegrationDialog } from './EditAwsOidcIntegrationDialog';
-import { UpdateAwsOidcThumbprint } from './UpdateAwsOidcThumbprint';
import { plugins, integrations } from './fixtures';
export default {
@@ -37,20 +36,6 @@ export function List() {
return ;
}
-export function UpdateAwsOidcThumbprintHoverTooltip() {
- return (
-
- );
-}
-
export function DeleteDialog() {
return (
.
- */
-
-import { useState } from 'react';
-import styled from 'styled-components';
-import {
- Text,
- Link as ExternalLink,
- Flex,
- Box,
- ButtonPrimary,
- Mark,
-} from 'design';
-import { TextSelectCopyMulti } from 'shared/components/TextSelectCopy';
-import { ToolTipInfo } from 'shared/components/ToolTip';
-import useAttempt from 'shared/hooks/useAttemptNext';
-import * as Icons from 'design/Icon';
-
-import {
- Integration,
- integrationService,
-} from 'teleport/services/integrations';
-import { splitAwsIamArn } from 'teleport/services/integrations/aws';
-import cfg from 'teleport/config';
-
-export function UpdateAwsOidcThumbprint({
- integration,
-}: {
- integration: Integration;
-}) {
- const { attempt, run } = useAttempt();
-
- const [thumbprint, setThumbprint] = useState('');
-
- function getThumbprint() {
- run(() => integrationService.fetchThumbprint().then(setThumbprint));
- }
-
- const { awsAccountId, arnStartingPart } = splitAwsIamArn(
- integration.spec.roleArn
- );
-
- const encodedOidcProviderArn = encodeURIComponent(
- `${arnStartingPart}${awsAccountId}:oidc-provider/${cfg.proxyCluster}`
- );
-
- return (
-
-
-
-
- This integration has no S3 bucket configured. When renewing your
- HTTPS certificate, if it has a different CA, a manual update of this
- integration's thumbprint is required.
-
- You may run into issues when the thumbprint is stale.
-
-
-
- Generate a New Thumbprint
-
-
- {thumbprint && (
-
- )}
- {attempt.status === 'failed' && (
-
- Error
- fetching thumbprint: some kind of error
-
- )}
-
-
-
-
- To update thumbprint:
- -
- - Go to your{' '}
-
- IAM Identity Provider
- {' '}
- dashboard
-
- -
- - On Thumbprints section click on Manage{' '}
- then click on Add Thumbprint
-
- - - Copy and paste the generated thumbprint
-
-
-
- );
-}
-
-const Ul = styled.ul`
- margin-left: 0;
- padding-left: 0;
- list-style: none;
-`;
diff --git a/web/packages/teleport/src/Integrations/fixtures.ts b/web/packages/teleport/src/Integrations/fixtures.ts
index 029fca37725d9..b4422fa01da7e 100644
--- a/web/packages/teleport/src/Integrations/fixtures.ts
+++ b/web/packages/teleport/src/Integrations/fixtures.ts
@@ -150,17 +150,6 @@ export const integrations: Integration[] = [
statusCode: IntegrationStatusCode.Running,
spec: { roleArn: '', issuerS3Prefix: '', issuerS3Bucket: '' },
},
- {
- resourceType: 'integration',
- name: 'aws',
- kind: IntegrationKind.AwsOidc,
- statusCode: IntegrationStatusCode.Running,
- spec: {
- roleArn: 'some-role-arn',
- issuerS3Prefix: 'some-prefix',
- issuerS3Bucket: 'some-bucket',
- },
- },
];
export const externalAuditStorage: ExternalAuditStorage = {
diff --git a/web/packages/teleport/src/config.test.ts b/web/packages/teleport/src/config.test.ts
index ac1cacaefe3c2..462e1610cf1ea 100644
--- a/web/packages/teleport/src/config.test.ts
+++ b/web/packages/teleport/src/config.test.ts
@@ -37,21 +37,6 @@ test('getDeployServiceIamConfigureScriptPath formatting', async () => {
);
});
-test('getAwsOidcConfigureIdpScriptUrl formatting with s3 fields', async () => {
- const params: UrlAwsOidcConfigureIdp = {
- integrationName: 'int-name',
- roleName: 'role-arn',
- s3Bucket: 's3-bucket',
- s3Prefix: 's3-prefix',
- };
- const base =
- 'http://localhost/v1/webapi/scripts/integrations/configure/awsoidc-idp.sh?';
- const expected = `integrationName=int-name&role=role-arn&s3Bucket=s3-bucket&s3Prefix=s3-prefix`;
- expect(cfg.getAwsOidcConfigureIdpScriptUrl(params)).toBe(
- `${base}${expected}`
- );
-});
-
test('getAwsOidcConfigureIdpScriptUrl formatting, without s3 fields', async () => {
const params: UrlAwsOidcConfigureIdp = {
integrationName: 'int-name',
diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts
index b84b154a46b8d..226e7faa619bd 100644
--- a/web/packages/teleport/src/config.ts
+++ b/web/packages/teleport/src/config.ts
@@ -532,10 +532,7 @@ const cfg = {
},
getAwsOidcConfigureIdpScriptUrl(p: UrlAwsOidcConfigureIdp) {
- let path = cfg.api.awsConfigureIamScriptOidcIdpPath;
- if (p.s3Bucket && p.s3Prefix) {
- path += '&s3Bucket=:s3Bucket&s3Prefix=:s3Prefix';
- }
+ const path = cfg.api.awsConfigureIamScriptOidcIdpPath;
return cfg.baseUrl + generatePath(path, { ...p });
},
diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts
index 6d55ea28d279a..025472b22bc90 100644
--- a/web/packages/teleport/src/services/integrations/types.ts
+++ b/web/packages/teleport/src/services/integrations/types.ts
@@ -56,8 +56,8 @@ export enum IntegrationKind {
}
export type IntegrationSpecAwsOidc = {
roleArn: string;
- issuerS3Prefix: string;
- issuerS3Bucket: string;
+ issuerS3Prefix?: string;
+ issuerS3Bucket?: string;
};
export enum IntegrationStatusCode {
@@ -293,8 +293,6 @@ export type ListAwsRdsDatabaseResponse = {
export type IntegrationUpdateRequest = {
awsoidc: {
roleArn: string;
- issuerS3Bucket: string;
- issuerS3Prefix: string;
};
};