diff --git a/src/Components/CreateImageWizard/steps/Review/components/NoUsersAlert.tsx b/src/Components/CreateImageWizard/steps/Review/components/NoUsersAlert.tsx
new file mode 100644
index 0000000000..8d99cd854b
--- /dev/null
+++ b/src/Components/CreateImageWizard/steps/Review/components/NoUsersAlert.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+
+import { Alert, Content } from '@patternfly/react-core';
+
+import { useAppSelector } from '@/store/hooks';
+import {
+ selectImageTypes,
+ selectIsImageMode,
+ selectUsers,
+ type SupportedImageTypes,
+} from '@/store/slices/wizard';
+
+const DISK_IMAGE_TYPES: SupportedImageTypes[] = ['guest-image', 'aws', 'ami'];
+
+const NoUsersAlert = () => {
+ const isImageMode = useAppSelector(selectIsImageMode);
+ const imageTypes = useAppSelector(selectImageTypes);
+ const users = useAppSelector(selectUsers);
+
+ const hasUser = users.some((user) => (user.name || '').trim() !== '');
+ const isDiskImage = imageTypes.some((imageType) =>
+ DISK_IMAGE_TYPES.includes(imageType),
+ );
+
+ if (!isImageMode || !isDiskImage || hasUser) {
+ return null;
+ }
+
+ return (
+
+
+ This image has no user accounts, so you won't be able to log in to
+ it directly. To log in, use cloud-init to create a user when you launch
+ the image, or go back to the Users step and add one now.
+
+
+ );
+};
+
+export default NoUsersAlert;
diff --git a/src/Components/CreateImageWizard/steps/Review/components/index.ts b/src/Components/CreateImageWizard/steps/Review/components/index.ts
index 636233723a..630bf434ff 100644
--- a/src/Components/CreateImageWizard/steps/Review/components/index.ts
+++ b/src/Components/CreateImageWizard/steps/Review/components/index.ts
@@ -3,5 +3,6 @@ export { default as ContentOverview } from './Content';
export { default as ImageOverview } from './ImageOverview';
export { default as RepeatableBuild } from './RepeatableBuild';
export { default as Registration } from './Registration';
+export { default as NoUsersAlert } from './NoUsersAlert';
export { default as ReadyToBuildAlert } from './ReadyToBuildAlert';
export { default as Security } from './Security';
diff --git a/src/Components/CreateImageWizard/steps/Review/components/tests/NoUsersAlert.test.tsx b/src/Components/CreateImageWizard/steps/Review/components/tests/NoUsersAlert.test.tsx
new file mode 100644
index 0000000000..06ca554f9a
--- /dev/null
+++ b/src/Components/CreateImageWizard/steps/Review/components/tests/NoUsersAlert.test.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+
+import { screen } from '@testing-library/react';
+
+import { initialState } from '@/store/slices/wizard';
+import { renderWithRedux, type WizardStateOverrides } from '@/test/testUtils';
+
+import NoUsersAlert from '../NoUsersAlert';
+
+const testUser = {
+ name: 'testuser',
+ password: '',
+ ssh_key: '',
+ isAdministrator: false,
+ groups: [],
+ hasPassword: false,
+};
+
+const imageModeOverrides = (
+ overrides: WizardStateOverrides = {},
+): WizardStateOverrides => ({
+ details: {
+ ...initialState.details,
+ blueprint: { ...initialState.details.blueprint, mode: 'image' },
+ },
+ output: {
+ ...initialState.output,
+ imageTypes: ['guest-image'],
+ },
+ ...overrides,
+});
+
+describe('NoUsersAlert', () => {
+ test('warns when a disk image has no users', async () => {
+ renderWithRedux(, imageModeOverrides());
+
+ expect(await screen.findByText(/no users added/i)).toBeInTheDocument();
+ expect(screen.getByText(/cloud-init/i)).toBeInTheDocument();
+ });
+
+ test('does not warn for the container installer', () => {
+ renderWithRedux(
+ ,
+ imageModeOverrides({
+ output: {
+ ...initialState.output,
+ imageTypes: ['bootable-container-iso'],
+ },
+ }),
+ );
+
+ expect(screen.queryByText(/no users added/i)).not.toBeInTheDocument();
+ });
+
+ test('does not warn when a user is configured', () => {
+ renderWithRedux(
+ ,
+ imageModeOverrides({
+ system: {
+ ...initialState.system,
+ users: [testUser],
+ },
+ }),
+ );
+
+ expect(screen.queryByText(/no users added/i)).not.toBeInTheDocument();
+ });
+
+ test('does not warn in package mode', () => {
+ renderWithRedux(, {
+ output: {
+ ...initialState.output,
+ imageTypes: ['guest-image'],
+ },
+ });
+
+ expect(screen.queryByText(/no users added/i)).not.toBeInTheDocument();
+ });
+});
diff --git a/src/Components/CreateImageWizard/steps/Review/index.tsx b/src/Components/CreateImageWizard/steps/Review/index.tsx
index c437367b05..2e1b82c8e1 100644
--- a/src/Components/CreateImageWizard/steps/Review/index.tsx
+++ b/src/Components/CreateImageWizard/steps/Review/index.tsx
@@ -9,6 +9,7 @@ import {
AdvancedSettingsOverview,
ContentOverview,
ImageOverview,
+ NoUsersAlert,
ReadyToBuildAlert,
Registration,
RepeatableBuild,
@@ -33,6 +34,7 @@ const ReviewStep = () => {
return (
<>
+
diff --git a/src/Components/CreateImageWizard/steps/Users/index.tsx b/src/Components/CreateImageWizard/steps/Users/index.tsx
index b45498ae30..8f70d01201 100644
--- a/src/Components/CreateImageWizard/steps/Users/index.tsx
+++ b/src/Components/CreateImageWizard/steps/Users/index.tsx
@@ -5,7 +5,6 @@ import { Content, Title } from '@patternfly/react-core';
import { CustomizationLabels } from '@/Components/sharedComponents/CustomizationLabels';
import { useAppSelector } from '@/store/hooks';
import { selectIsOnPremise } from '@/store/slices/env';
-import { selectBlueprintMode } from '@/store/slices/wizard';
import UserInfo from './components/UserInfo';
@@ -14,7 +13,6 @@ type UsersStepProps = {
};
const UsersStep = ({ attemptedNext }: UsersStepProps) => {
- const blueprintMode = useAppSelector(selectBlueprintMode);
const isOnPremise = useAppSelector(selectIsOnPremise);
return (
<>
@@ -27,9 +25,16 @@ const UsersStep = ({ attemptedNext }: UsersStepProps) => {
Create user accounts to manage access to your image. All usernames
must be unique.
{/* TO DO: learn more about accessing your SSH keys link */}
- {isOnPremise &&
- blueprintMode === 'image' &&
- ' You must create a user during the image build process to be able to log in.'}
+ {isOnPremise && (
+ <>
+ {' '}
+ Passwords are stored in plain text on this host. To store a hashed
+ password instead, generate one with
+ openssl passwd -6
+ {' '}
+ and enter the result.
+ >
+ )}
diff --git a/src/Components/CreateImageWizard/steps/Users/tests/Users.test.tsx b/src/Components/CreateImageWizard/steps/Users/tests/Users.test.tsx
index 9fb6552f2a..075a89e257 100644
--- a/src/Components/CreateImageWizard/steps/Users/tests/Users.test.tsx
+++ b/src/Components/CreateImageWizard/steps/Users/tests/Users.test.tsx
@@ -96,4 +96,33 @@ describe('Users Component', () => {
);
});
});
+
+ describe('Password guidance', () => {
+ test('shows plain text warning and openssl hint on-prem', async () => {
+ renderWithRedux(
+ ,
+ {},
+ {
+ preloadedState: {
+ env: { isOnPremise: true },
+ },
+ },
+ );
+
+ expect(
+ await screen.findByText(/stored in plain text on this host/i),
+ ).toBeInTheDocument();
+ expect(screen.getByText('openssl passwd -6')).toBeInTheDocument();
+ });
+
+ test('does not show the plain text warning in the hosted service', async () => {
+ renderWithRedux(, {});
+
+ await screen.findByText(/create user accounts/i);
+
+ expect(
+ screen.queryByText(/stored in plain text/i),
+ ).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/src/Components/CreateImageWizard/utilities/useValidation.tsx b/src/Components/CreateImageWizard/utilities/useValidation.tsx
index 1df52e35ea..ec7826edb5 100644
--- a/src/Components/CreateImageWizard/utilities/useValidation.tsx
+++ b/src/Components/CreateImageWizard/utilities/useValidation.tsx
@@ -881,8 +881,6 @@ const validateSshKey = (userSshKey: string): string => {
export function useUsersValidation(): UsersStepValidation {
const environments = useAppSelector(selectImageTypes);
- const blueprintMode = useAppSelector(selectBlueprintMode);
- const isOnPremise = useAppSelector(selectIsOnPremise);
const users = useAppSelector(selectUsers);
const userGroups = useAppSelector(selectUserGroups);
const errors: { [key: string]: { [key: string]: string } } = {};
@@ -891,14 +889,6 @@ export function useUsersValidation(): UsersStepValidation {
users.length === 0 ||
(users.length === 1 && (users[0].name || '').trim() === '')
) {
- if (isOnPremise && blueprintMode === 'image') {
- return {
- errors: {},
- warnings: {},
- disabledNext: true,
- };
- }
-
return {
errors: {},
warnings: {},