Skip to content

feat(system): redo password handling for remote root, allow SSH password auth#124

Merged
water-sucks merged 3 commits intonix-community:mainfrom
water-sucks:fix-sudo-input
Nov 3, 2025
Merged

feat(system): redo password handling for remote root, allow SSH password auth#124
water-sucks merged 3 commits intonix-community:mainfrom
water-sucks:fix-sudo-input

Conversation

@water-sucks
Copy link
Copy Markdown
Collaborator

@water-sucks water-sucks commented Nov 3, 2025

Description

sudo and other root command input is just broken right now due to required interactive input.

As such, this changes around some SSH handling, and also allows passwords to be used as an authentication mechanism.
If a password is used for access, then this is reused when --remote-sudo is passed. Additionally, it asks for a password once, and then uses sudo stdin.

This may be broken for doas, and doas still requires command input and a PTY, but this is a step in the right direction.

Also, this removes passing stdin to SSH processes, since no remote commands should need a stdin at this time. This fixes some input handling for remote confirmation as well.

Summary by CodeRabbit

  • New Features

    • Password-based SSH authentication as a supported fallback when agent keys aren't available.
    • Automatic remote root password setup/verification when remote root escalation is requested.
  • Improvements

    • SSH connection errors are consistently logged and surfaced.
    • Activation confirmation now prompts uniformly for all targets.
  • Documentation

    • New "Remote Deployments" guide covering remote build/deploy flags, authentication, and root escalation behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 3, 2025

Walkthrough

SSH deployment flow now supports password fallback to SSH agent, interactive PTY-based root password handling, and an EnsureRemoteRootPassword check; apply flow always prompts for activation and invokes remote-root verification when enabled. Documentation updated with a Remote Deployments section.

Changes

Cohort / File(s) Summary
SSH Authentication & Execution
internal/system/ssh.go
Replaced host with address and added password field. Added password-based auth fallback with promptForPassword(), updated NewSSHSystem to try SSH agent then password, updated KnownHosts callback. Added EnsureRemoteRootPassword(rootCmd string) error and testRemoteRoot() helpers. Extended Run() to inject password for root commands and added PTY allocation helper requestRootPasswordPTY(). Address() now uses address. Improved Close() defensive cleanup.
Apply Activation Flow
cmd/apply/apply.go
Removed SSH-agent special-case handling and remote-host conditional skip for activation confirmation; now always prompts. After target setup, if target is an SSHSystem and --remote-root enabled, call EnsureRemoteRootPassword() and propagate/log errors.
Remote Deployments Documentation
doc/src/overview.md
Added "Remote Deployments" section describing --build-host, --target-host, --remote-root, escalation requirements (root or root_command), non-root behavior, SSH agent/key recommendations, and notes about password-based access.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Apply
    participant SSH as SSHSystem
    participant Remote as Remote Host
    participant Terminal

    User->>Apply: run apply (--target-host, --remote-root?)
    Apply->>SSH: Initialize SSHSystem (NewSSHSystem)
    SSH->>Remote: Try SSH agent auth
    alt Agent auth succeeds
        Remote-->>SSH: auth OK
    else Agent missing/failed
        SSH->>Terminal: promptForPassword()
        Terminal->>User: request SSH password
        User-->>Terminal: enter password
        Terminal-->>SSH: password bytes
        SSH->>Remote: attempt password auth
        Remote-->>SSH: auth OK
    end

    SSH-->>Apply: connection ready
    Apply->>User: prompt activation confirmation
    User-->>Apply: confirm

    rect rgb(230,245,255)
    Note over Apply,SSH: Remote root setup (if --remote-root)
    Apply->>SSH: EnsureRemoteRootPassword(rootCmd)
    SSH->>Terminal: promptForPassword() for sudo/root
    Terminal->>User: request root/sudo password
    User-->>Terminal: enter password
    Terminal-->>SSH: root password bytes
    SSH->>Remote: testRemoteRoot (sudo + password)
    Remote-->>SSH: validation OK
    SSH-->>Apply: remote root configured
    end

    Apply->>SSH: Run deployment commands
    SSH->>Remote: execute (inject password if needed)
    Remote-->>Apply: done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus review areas:
    • internal/system/ssh.go — credential lifecycle, secure memory handling of password []byte, auth fallback, KnownHosts callback changes, PTY allocation and terminal raw mode restore.
    • cmd/apply/apply.go — correctness of unconditional prompt and sequencing of EnsureRemoteRootPassword() with error propagation.
    • Tests and edge cases around non-interactive runs, SSH agent absence, and sudo prompts.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(system): redo password handling for remote root, allow SSH password auth' accurately summarizes the main changes in the changeset. It clearly identifies the primary objective: redesigning password handling for remote root operations and enabling SSH password authentication. This aligns well with the substantial modifications across cmd/apply/apply.go, internal/system/ssh.go, and doc/src/overview.md.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c259f7 and 50aa35d.

📒 Files selected for processing (3)
  • cmd/apply/apply.go (3 hunks)
  • doc/src/overview.md (1 hunks)
  • internal/system/ssh.go (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
cmd/apply/apply.go (2)
internal/cmd/utils/confirmation.go (1)
  • ConfirmationInput (11-28)
internal/system/ssh.go (1)
  • SSHSystem (30-39)
internal/system/ssh.go (2)
internal/logger/logger.go (1)
  • Logger (12-27)
internal/system/runner.go (1)
  • NewCommand (25-34)
🪛 LanguageTool
doc/src/overview.md

[style] ~94-~94: Consider a more concise word here.
Context: ...defined in the root_command setting. In order to access remote machines, prefer using an...

(IN_ORDER_TO_PREMIUM)


[style] ~96-~96: Consider using a different verb to strengthen your wording.
Context: ...t recommended, and can be buggy. If you find any issues, report them on the issue tr...

(FIND_ENCOUNTER)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build/Test

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/system/ssh.go (1)

173-185: Consider parameterizing stdin for password prompts

The function hard-codes os.Stdin for terminal detection and password reading. This works for the current use case but reduces flexibility if stdin needs to be redirected or mocked for testing.

Consider accepting stdin as a parameter:

-func promptForPassword(username string, address string) ([]byte, error) {
-	fd := int(os.Stdin.Fd())
+func promptForPassword(username string, address string, stdin *os.File) ([]byte, error) {
+	fd := int(stdin.Fd())
	if !term.IsTerminal(fd) {
		return nil, fmt.Errorf("cannot prompt for password: stdin is not a terminal")
	}
	
-	fmt.Fprintf(os.Stderr, "Password for %s@%s: ", username, address)
-	_ = os.Stdin.Sync()
+	fmt.Fprintf(os.Stderr, "Password for %s@%s: ", username, address)
+	_ = stdin.Sync()
	
-	bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
+	bytePassword, err := term.ReadPassword(fd)
	fmt.Fprintln(os.Stderr)
	return bytePassword, err
}

Then update callers on lines 101, 152 to pass os.Stdin explicitly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50aa35d and f36bded.

📒 Files selected for processing (3)
  • cmd/apply/apply.go (3 hunks)
  • doc/src/overview.md (1 hunks)
  • internal/system/ssh.go (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
cmd/apply/apply.go (2)
internal/cmd/utils/confirmation.go (1)
  • ConfirmationInput (11-28)
internal/system/ssh.go (1)
  • SSHSystem (30-39)
internal/system/ssh.go (2)
internal/logger/logger.go (1)
  • Logger (12-27)
internal/system/runner.go (1)
  • NewCommand (25-34)
🪛 LanguageTool
doc/src/overview.md

[style] ~94-~94: Consider a more concise word here.
Context: ...defined in the root_command setting. In order to access remote machines, prefer using an...

(IN_ORDER_TO_PREMIUM)


[style] ~96-~96: Consider using a different verb to strengthen your wording.
Context: ...t recommended, and can be buggy. If you find any issues, report them on the issue tr...

(FIND_ENCOUNTER)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build/Test
🔇 Additional comments (10)
cmd/apply/apply.go (3)

179-180: LGTM: Consistent error handling

The removal of special-case SSH connection error handling provides consistent logging and error propagation for both target and build hosts.

Also applies to: 228-229


407-418: LGTM: Unconditional activation confirmation improves safety

The change to always prompt for activation confirmation (regardless of deployment target) is a safer default that prevents accidental remote activations.


420-426: LGTM: Remote root password verification integrated correctly

The integration of EnsureRemoteRootPassword is well-placed after successful SSH connection setup and only executes when the --remote-root flag is specified. Error handling and logging are appropriate.

doc/src/overview.md (1)

79-97: LGTM: Clear documentation of remote deployment features

The new "Remote Deployments" section effectively documents the SSH-based deployment workflow, root escalation requirements, and authentication methods. The caveat about password-based authentication being "potentially buggy" appropriately sets user expectations.

internal/system/ssh.go (6)

30-39: LGTM: Struct updated to support password authentication

The addition of address and password fields appropriately supports the new authentication flow. The password field stores credentials in memory, which is necessary for sudo password injection but should be documented as sensitive.


79-108: Verify the silent SSH agent fallback behavior

The authentication flow now silently falls backs to password auth when the SSH agent is unavailable (line 88-89). While this provides a better user experience, it may mask configuration issues where users expect agent-based authentication.

Consider whether a warning-level log message would be more appropriate than debug-level for the agent fallback.


141-164: LGTM: Root password verification well-implemented

The EnsureRemoteRootPassword method correctly:

  • Short-circuits when password is already available
  • Prompts once and stores the credential
  • Verifies the password works before returning

This provides good UX by catching authentication failures early.


476-476: LGTM: Address method updated for field rename

Correctly uses s.address to match the struct field changes.


503-505: LGTM: Defensive resource cleanup and command filtering

The nil check before closing s.conn prevents panics, and the removal of "run0" from isRootCommand appears intentional.

Also applies to: 520-520


274-316: stdin forwarding logic is correct—no action required

The code properly sets session.Stdin = cmd.Stdin (line 290) before calling requestRootPasswordPTY (line 308), addressing the previous review concern. The password injection path uses the -S flag with stdin correctly, and the PTY path forwards stdin after setting up the terminal.

The FIXME comment (lines 305-307) documents a known PTY issue with first-byte loss as an existing limitation, not a bug requiring immediate resolution.

@water-sucks water-sucks merged commit 844429f into nix-community:main Nov 3, 2025
2 checks passed
@water-sucks water-sucks deleted the fix-sudo-input branch November 3, 2025 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant