fix(auth): propagate TTY state to subprocesses for SSO device flow in workflows#2126
fix(auth): propagate TTY state to subprocesses for SSO device flow in workflows#2126
Conversation
… workflows When atmos runs workflow steps via ExecuteShellCommand, stderr is wrapped in a MaskWriter pipe. This causes subprocess TTY detection (os.Stderr.Fd()) to return a pipe descriptor instead of a terminal, breaking AWS SSO device authorization flow which requires an interactive TTY to display the verification URL and code to the user. Fix 1 (pkg/auth/providers/aws/sso.go): - isInteractive() now respects --force-tty / ATMOS_FORCE_TTY via viper, allowing callers to explicitly override TTY detection when needed. Fix 2 (internal/exec/shell_utils.go): - ExecuteShellCommand now detects when the parent process has a real TTY on stderr and automatically injects ATMOS_FORCE_TTY=true into the subprocess environment when the variable is not already set. - This ensures workflow steps like `command: auth login` behave interactively when the user is running atmos from a terminal. Tests: - TestIsInteractive_ForceTTY: verifies force-tty viper key is respected - TestEnvKeyIsSet: table-driven tests for the new envKeyIsSet helper - TestExecuteShellCommand: two new subtests covering ATMOS_FORCE_TTY preservation and non-injection in non-TTY environments Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dependency Review✅ No vulnerabilities or license issues found.Scanned FilesNone |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughExec now propagates TTY state to subprocesses by injecting ATMOS_FORCE_TTY into merged env when the parent has a real TTY and the key isn't present; AWS SSO's isInteractive() can be forced via a viper Changes
Sequence Diagram(s)sequenceDiagram
participant Parent as Parent Process
participant Exec as ExecuteShellCommand
participant Sub as Subprocess
Parent->>Exec: call ExecuteShellCommand(cmd, stepEnv)
Exec->>Exec: merge envs (parent + stepEnv)
Exec->>Exec: isTTY() -> true?
alt parent has TTY and env key not set
Exec->>Exec: add ATMOS_FORCE_TTY=true to env
end
Exec->>Sub: spawn subprocess with merged env
Sub->>Sub: detects ATMOS_FORCE_TTY in env
sequenceDiagram
participant SSO as AWS SSO module
participant Config as Viper config
participant Term as isTTY()
SSO->>Config: read "force-tty"
alt force-tty set
Config-->>SSO: true
SSO->>SSO: isInteractive() returns true
else
Config-->>SSO: not set
SSO->>Term: call isTTY()
Term-->>SSO: true/false
SSO->>SSO: isInteractive() returns Term result
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
pkg/auth/providers/aws/sso_test.go (1)
818-819: Restore prior Viper state in cleanup (avoid cross-test coupling).This test currently forces cleanup to
false, which can mutate global config state for later tests. Restore the previous value instead.Suggested patch
func TestIsInteractive_ForceTTY(t *testing.T) { // Test that force-tty viper setting overrides the TTY check. + prevForceTTY := viper.GetBool("force-tty") viper.Set("force-tty", true) - t.Cleanup(func() { viper.Set("force-tty", false) }) + t.Cleanup(func() { viper.Set("force-tty", prevForceTTY) }) result := isInteractive() assert.True(t, result, "isInteractive should return true when force-tty is set") }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/auth/providers/aws/sso_test.go` around lines 818 - 819, The cleanup currently unconditionally sets viper "force-tty" to false which mutates global state; instead capture the prior value before changing it (e.g., prev := viper.Get("force-tty") or viper.GetBool("force-tty")), set viper.Set("force-tty", true) for the test, and in the t.Cleanup restore the original value using viper.Set("force-tty", prev) (or cast prev back to bool) so the global Viper state is returned exactly to its prior value; update the code around viper.Set and t.Cleanup in the aws SSO test to use this save-and-restore pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/exec/shell_utils_test.go`:
- Around line 625-626: The test currently sets ATMOS_FORCE_TTY to an empty
string (t.Setenv("ATMOS_FORCE_TTY", "")) which leaves the key present and yields
a false positive; replace those empty-set calls with removal of the env var
(e.g., call os.Unsetenv("ATMOS_FORCE_TTY") or ensure the env key is absent)
before invoking ExecuteShellCommand so the shell expansion
`${ATMOS_FORCE_TTY:-not-set}` truly falls back to the default; apply the same
fix for the other occurrences around lines 630-642 and keep assertions against
ExecuteShellCommand's output (behavior) rather than presence of the variable
(implementation).
---
Nitpick comments:
In `@pkg/auth/providers/aws/sso_test.go`:
- Around line 818-819: The cleanup currently unconditionally sets viper
"force-tty" to false which mutates global state; instead capture the prior value
before changing it (e.g., prev := viper.Get("force-tty") or
viper.GetBool("force-tty")), set viper.Set("force-tty", true) for the test, and
in the t.Cleanup restore the original value using viper.Set("force-tty", prev)
(or cast prev back to bool) so the global Viper state is returned exactly to its
prior value; update the code around viper.Set and t.Cleanup in the aws SSO test
to use this save-and-restore pattern.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
internal/exec/shell_utils.gointernal/exec/shell_utils_test.gopkg/auth/providers/aws/sso.gopkg/auth/providers/aws/sso_test.go
…ar properly
t.Setenv("ATMOS_FORCE_TTY", "") leaves the key present in the environment.
envKeyIsSet then matches it and skips auto-injection — not because there is
no TTY, but because the key exists. This caused ${VAR:-default} to expand
to the fallback for the wrong reason (empty value, not absent key), masking
the incorrect skip path.
Replace both t.Setenv("ATMOS_FORCE_TTY", "") calls with os.LookupEnv +
os.Unsetenv + t.Cleanup so the key is truly absent before the subprocess
runs. Tests now verify the correct behavior: auto-injection is skipped
because the test runner has no TTY, not because a stale empty value was
present in the parent environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2126 +/- ##
==========================================
+ Coverage 76.52% 76.55% +0.02%
==========================================
Files 832 832
Lines 79441 79451 +10
==========================================
+ Hits 60792 60820 +28
+ Misses 14856 14837 -19
- Partials 3793 3794 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
These changes were released in v1.208.0. |
…raform-plan * osterman/native-ci-terraform: (28 commits) feat: Add source cache TTL for JIT-vendored components (#2138) feat: Per-target version overrides in vendor manifests (#2141) docs: Add PRD for browser-based auth in aws/user identity (#1887) docs: Add EKS kubeconfig authentication integration PRD (#1884) fix: correct marketplace.json schema and update docs with install/uninstall commands (#2142) fix: propagate auth to all YAML functions in multi-component execution (#2140) fix: Use atmos_component for source provisioner workdir paths (#2137) Fix identity prompts to respect --interactive flag (#2130) Increase PR size thresholds to accommodate AI-assisted development (#2136) docs: Add Azure authentication provider documentation (#2132) fix: propagate component-type level dependencies through stack processor (#2127) fix: Add retry and missing workflow step properties to all schema copies (#2113) Exclude unsupported windows/arm from goreleaser build matrix (#2133) Add AI Agent Skills for LLM-Powered Infrastructure Development (#2121) Fix: Convert toolchain paths to absolute in PATH to resolve exec.LookPath failures (#2095) Fix workdir collision for component instances sharing base component (#2093) fix(auth): propagate TTY state to subprocesses for SSO device flow in workflows (#2126) fix(security): prevent SSRF in GitHub OIDC token URL handling (CWE-918) (#2106) Fix #2112: add workflow_retry definition and retry property to workflow step schema (#2114) fix(auth): auto-detect GitHub Actions WIF with proper audience, host validation, and lazy GSM init (#2109) ...
|
These changes were released in v1.208.1-test.9. |
|
These changes were released in v1.208.1-test.10. |
What
Fixes
atmos auth loginfailing with "AWS SSO device flow requires an interactive terminal (TTY)" when called as a step inside an atmos workflow.Why
When atmos runs workflow command steps via
ExecuteShellCommand,cmd.Stderris wrapped in aMaskWriter(for secret masking). This creates a pipe between the parent and subprocess, so the subprocess'sterm.IsTerminal(os.Stderr.Fd())returnsfalse— even when the user is sitting at an interactive terminal. As a result, the SSO device auth flow'sisInteractive()guard blocks authentication.Two-layer fix:
pkg/auth/providers/aws/sso.go—isInteractive()now checks--force-tty/ATMOS_FORCE_TTYvia viper before falling back to raw TTY detection. This allows any caller to explicitly signal an interactive context.internal/exec/shell_utils.go—ExecuteShellCommandnow checks whether the parent process has a real TTY on stderr. When it does (andATMOS_FORCE_TTYis not already set), it injectsATMOS_FORCE_TTY=trueinto the subprocess environment. This propagates the interactive context through the MaskWriter pipe automatically.Behavior matrix after fix:
atmos auth loginfrom terminalcommand: auth loginfrom terminalATMOS_FORCE_TTY=trueset manuallyTests
TestIsInteractive_ForceTTY— verifiesforce-ttyviper key enables interactive modeTestEnvKeyIsSet— table-driven tests for theenvKeyIsSethelper (6 cases)TestExecuteShellCommand— two new subtests:ATMOS_FORCE_TTY preserved when explicitly set in step env— user-set value is never overriddenATMOS_FORCE_TTY not auto-injected in non-TTY environment— CI/pipe environments stay non-interactiveReferences
MaskWriterwrapsos.Stderras a pipe inExecuteShellCommand, breakingterm.IsTerminal()in subprocess even when the parent process has a real TTYSummary by CodeRabbit
Bug Fixes
Tests
Chores