upcoming: [UIE-10333] - Allow Creating Secure Linodes without Root Password#13516
upcoming: [UIE-10333] - Allow Creating Secure Linodes without Root Password#13516harsh-akamai wants to merge 8 commits intolinode:developfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Enables a feature-flagged “passwordless Linode” flow in Manager UI so users can create/rebuild/deploy-from-image disks using SSH key auth without requiring a root password, while updating shared validation and API types to support new/adjusted payload shapes.
Changes:
- Adds
passwordlessLinodesfeature flag and auseIsPasswordLessLinodesEnabledhelper hook (plus dev-tools toggle + tests). - Updates Manager UI + validation selection for Create, Rebuild, and Create Disk-from-image to allow SSH-key-only authentication when flagged on.
- Extends shared validation/API typings (e.g.,
kernel,boot_size) and introduces new validation schema variants for disk-from-image.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/validation/src/linodes.schema.ts | Makes root_pass optional in base schemas; adds disk-from-image schema variants; adds kernel/boot_size fields. |
| packages/manager/src/utilities/linodes.ts | Adds useIsPasswordLessLinodesEnabled feature-flag hook. |
| packages/manager/src/utilities/linodes.test.ts | Adds unit tests for the new feature-flag hook. |
| packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/CreateDiskDrawer.tsx | Switches disk validation schema based on mode + passwordless flag. |
| packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/ImageAndPassword.tsx | Updates layout/error presentation when passwordless is enabled. |
| packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/utils.ts | Adds passwordless-aware rebuild validation resolver selection. |
| packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/Password.tsx | Adds showErrorText option to control inline error rendering. |
| packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/LinodeRebuildForm.tsx | Threads passwordless flag into form context and updates authentication UI layout. |
| packages/manager/src/features/Linodes/LinodeCreate/utilities.ts | Adds passwordless flag to RHF context typing; fixes a typo in comment. |
| packages/manager/src/features/Linodes/LinodeCreate/schemas.ts | Adds helpers to wrap schemas with root password required/optional behavior. |
| packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts | Makes schema selection depend on passwordless flag; fixes typos in comments. |
| packages/manager/src/features/Linodes/LinodeCreate/index.tsx | Wires passwordless flag into create form context; updates Security import path. |
| packages/manager/src/features/Linodes/LinodeCreate/Security/Security.tsx | Refactors Security section into Password + SSHKeys components and reorders based on flag. |
| packages/manager/src/features/Linodes/LinodeCreate/Security/Security.test.tsx | Fixes type import path after Security refactor. |
| packages/manager/src/features/Linodes/LinodeCreate/Security/SSHKeys.tsx | New component encapsulating SSH key selection + permissions. |
| packages/manager/src/features/Linodes/LinodeCreate/Security/Password.tsx | New component encapsulating root password input + passwordless error layout. |
| packages/manager/src/featureFlags.ts | Adds passwordlessLinodes to flags interface. |
| packages/manager/src/dev-tools/FeatureFlagTool.tsx | Adds dev-tools toggle for passwordlessLinodes. |
| packages/api-v4/src/linodes/types.ts | Adds kernel/boot_size to CreateLinodeRequest; replaces RebuildRequest infer-type with explicit interface. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // .concat(rootPasswordValidation), | ||
| otherwise: (schema) => schema.notRequired(), | ||
| }), | ||
| root_pass: string(), |
There was a problem hiding this comment.
CreateLinodeDiskSchema no longer enforces root_pass when image is set. This schema is used by the API client (packages/api-v4/src/linodes/disks.ts uses setData(data, CreateLinodeDiskSchema)), so disk creation from an image can now be submitted without a password and without SSH keys, which likely just defers the error to the API. Suggest restoring conditional validation on root_pass when image is present (or adding a dedicated passwordless schema variant and keeping the base schema strict).
| root_pass: string(), | |
| root_pass: string().when('image', { | |
| is: (value: any) => Boolean(value), | |
| then: (schema) => | |
| schema.required( | |
| 'You must provide a root password when deploying from an image.', | |
| ), | |
| // .concat(rootPasswordValidation), | |
| otherwise: (schema) => schema.notRequired(), | |
| }), |
| kernel: string().notRequired(), | ||
| boot_size: number().notRequired(), | ||
| swap_size: number().notRequired(), |
There was a problem hiding this comment.
kernel and boot_size are added as optional fields but are not marked nullable. In packages/api-v4/src/linodes/types.ts they’re typed as null | string / null | number, so passing null (which is allowed by the types) would fail schema validation. Consider using string().nullable().notRequired() / number().nullable().notRequired() (and aligning with how other nullable request fields like backup_id are modeled).
| kernel: string().notRequired(), | |
| boot_size: number().notRequired(), | |
| swap_size: number().notRequired(), | |
| kernel: string().nullable().notRequired(), | |
| boot_size: number().nullable().notRequired(), | |
| swap_size: number().nullable().notRequired(), |
| schema.test({ | ||
| name: 'root-pass-or-authorized-users', | ||
| message: | ||
| 'An SSH Key or a Root Password is required to create an instance. We recommend using an SSH Key for better security.', |
There was a problem hiding this comment.
The validation error message here says "required to create an instance", but this schema is used for creating a disk from an image. This is user-facing copy and is likely to be confusing in the disk drawer context; consider wording it specifically for disk creation (or using a more generic "to proceed" message).
| 'An SSH Key or a Root Password is required to create an instance. We recommend using an SSH Key for better security.', | |
| 'An SSH Key or a Root Password is required to create this disk. We recommend using an SSH Key for better security.', |
| export const RebuildLinodeSchema = object({ | ||
| image: string().required('An image is required.'), | ||
| root_pass: string().required('Password is required.'), | ||
| root_pass: string(), | ||
| authorized_keys: array().of(string().required()), | ||
| authorized_users: array().of(string().required()), |
There was a problem hiding this comment.
RebuildLinodeSchema no longer requires root_pass. This schema is used for request validation in packages/api-v4/src/linodes/actions.ts (setData(data, RebuildLinodeSchema)), so callers can now submit a rebuild without root_pass and without any SSH keys and only find out via a server-side 400. Consider keeping the previous requirement (e.g., require root_pass when both authorized_users and authorized_keys are empty) and introducing a separate passwordless schema variant for the UI feature-flagged flow instead of weakening the base schema.
| ), | ||
| otherwise: (schema) => schema.notRequired(), | ||
| }), | ||
| root_pass: string(), |
There was a problem hiding this comment.
CreateLinodeSchema now declares root_pass: string() with no conditional requirement. Since CreateLinodeSchema is used by the API client (packages/api-v4/src/linodes/linodes.ts uses setData(data, CreateLinodeSchema)), this allows creating an image-based Linode without root_pass or SSH keys and only fails at the API. Consider keeping the base schema’s previous behavior (require root_pass when deploying from an image unless SSH keys are provided), and if needed add a separate passwordless schema for the feature-flagged Manager flow.
| root_pass: string(), | |
| root_pass: string().when(['image', 'authorized_keys'], { | |
| is: (image: unknown, authorized_keys: unknown) => { | |
| const hasImage = Boolean(image); | |
| const keys = Array.isArray(authorized_keys) ? authorized_keys : []; | |
| const hasNoKeys = keys.length === 0; | |
| return hasImage && hasNoKeys; | |
| }, | |
| then: (schema) => | |
| schema.required('You must provide a root password or SSH keys.'), | |
| otherwise: (schema) => schema.notRequired(), | |
| }), |
| const { authorized_users } = context.parent; | ||
| const hasAuthorizedUsers = | ||
| Array.isArray(authorized_users) && authorized_users.length > 0; | ||
| return Boolean(value) || hasAuthorizedUsers; |
There was a problem hiding this comment.
In CreateLinodeDiskFromImageWithoutPasswordSchema, the root_pass requirement is waived only when authorized_users is non-empty, but the schema also supports authorized_keys. If a consumer supplies authorized_keys (public key strings) but not authorized_users, validation will still fail even though SSH-key auth is present. Consider checking both authorized_users and authorized_keys when deciding whether a password can be omitted.
| const { authorized_users } = context.parent; | |
| const hasAuthorizedUsers = | |
| Array.isArray(authorized_users) && authorized_users.length > 0; | |
| return Boolean(value) || hasAuthorizedUsers; | |
| const { authorized_users, authorized_keys } = context.parent; | |
| const hasAuthorizedUsers = | |
| Array.isArray(authorized_users) && authorized_users.length > 0; | |
| const hasAuthorizedKeys = | |
| Array.isArray(authorized_keys) && authorized_keys.length > 0; | |
| return Boolean(value) || hasAuthorizedUsers || hasAuthorizedKeys; |
packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/CreateDiskDrawer.tsx
Show resolved
Hide resolved
| is: (value: any) => !Array.isArray(value) || value.length === 0, | ||
| then: (schema) => | ||
| schema.required( | ||
| 'An SSH Key or a Root Password is required to create an instance. We recommend using an SSH Key for better security.' |
There was a problem hiding this comment.
The new passwordless validation error message says "required to create an instance", but this is the Linode rebuild flow (not creation). Since this message will be shown to end users, consider adjusting the wording to match the rebuild context (or using a generic message that doesn’t mention creation).
| 'An SSH Key or a Root Password is required to create an instance. We recommend using an SSH Key for better security.' | |
| 'An SSH Key or a Root Password is required. We recommend using an SSH Key for better security.' |
Cloud Manager UI test results🔺 3 failing tests on test run #7 ↗︎
Details
TroubleshootingUse this command to re-run the failing tests: pnpm cy:run -s "cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts,cypress/e2e/core/objectStorage/access-key.e2e.spec.ts,cypress/e2e/core/kubernetes/lke-create.spec.ts" |
|||||||||||||||||||||||
@pmakode-akamai This is an error from the API as it is not yet ready to support the changes. As long as the validation from Cloud Manager is working as expected this PR should be good |

Description 📝
This PR eliminates the root password requirement during Linode creation and supports SSH key-based authentication without disrupting existing customer workflows.
For Linode Create, Rebuild and Create Linode Disk flows
Changes 🔄
Scope 🚢
Upon production release, changes in this PR will be visible to:
Target release date 🗓️
Please specify a release date (and environment, if applicable) to guarantee timely review of this PR. If exact date is not known, please approximate and update it as needed.
Preview 📷
Linode Create
Linode Rebuild
Create Linode Disk
How to test 🧪
Prerequisites
Verification steps
Note
The API doesn’t currently support creating a Linode using only SSH keys. To verify the changes, we should ensure the validation behaves as expected. In cases where a root password isn’t provided, the API request should not include the
root_passparameter.Linode Create
Linode Rebuild Form and Create Linode Disk Drawer(From Image)
Author Checklists
As an Author, to speed up the review process, I considered 🤔
👀 Doing a self review
❔ Our contribution guidelines
🤏 Splitting feature into small PRs
➕ Adding a changeset
🧪 Providing/improving test coverage
🔐 Removing all sensitive information from the code and PR description
🚩 Using a feature flag to protect the release
👣 Providing comprehensive reproduction steps
📑 Providing or updating our documentation
🕛 Scheduling a pair reviewing session
📱 Providing mobile support
♿ Providing accessibility support
As an Author, before moving this PR from Draft to Open, I confirmed ✅