1password broken due to each fork calling setsid (without TTY)

We use 1password to store some of the secrets used in our playbooks. The CLI tool to retrieve has multiple methods for authorization/sessions, some set an environment variable and some are linked to the TTY. The recommended method (desktop app integration) uses the TTY, which doesn’t work in Ansible.

Expected behavior:

  • Call the op CLI tool to retrieve secrets, delegated to localhost
  • Get prompt by desktop app to approve this action
  • Subsequent calls to the op CLI caching this approval

Actual behavior:

  • Call the op CLI tool to retrieve secrets, delegated to localhost
  • Get prompt by desktop app to approve this action
  • Keep getting prompts for every call

After some debugging I traced this back to each worker calling setsid after forking, thus making it impossible to cache authorizations. Confirmed to be the culprit by locally patching the call to setpgrp, which makes it behave like expected.

This seems to have been introduced in commit 8127abbc298cabf04aaa89a478fc5e5e3432a6fc, and already discussed in some issues on GitHub (primarily #85536 it seems). Staring a new sessions is claimed to be intentional, “mostly to avoid child processes from accessing the parent TTY directly and corrupting data sent back”. The workaround there ended up being to loosen restrictions of sudo, which isn’t possible with 1password due to security reasons.

Ansible already already detaches the TTY from stdin/stdout/stderr for new forks, so to mess with the TTY a sub process would have to go out of its way to grab the file descriptors from the session leader. For our use case this would be desired for 1password, and acceptable for other local processes.

A setting to “downgrade” the forks from separate sessions to separate process groups would be ideal. Alternatively, creating a PTY that’s shared across the workers should allow 1password to at least cache it within on Ansible run, though I’ve not done much testing/investigation yet on that.

If I missed and/or misunderstood something, please do let me know :slight_smile:

1 Like

You can apparently use a 1password service account to circumvent this: community.general.onepassword prompting for password every time. · Issue #10792 · ansible-collections/community.general · GitHub

The “manual” sign-in works in a similar way, bypassing this session issue. From a security/compliance issue however these solutions are less than ideal, as the desktop integration provides a couple extra security guarantees (and also provides a better user experience). Actually solving the underlying issue instead of using a workaround would be preferred :sweat_smile:

Locally I’ve patched Ansible to use setpgrp instead of setsid, and have not yet ran into issues with it, at least not with our workflow. I could make a PR with this change as a flag/option if that’s helpful.

I don’t think that’d get us the desired behavior (also: setpgrp is deprecated). The setsid was added explicitly to block TTY inheritance for forked workers- a source of incredibly difficult to repro/fix bugs for Ansible’s entire existence when many concurrently forked processes share it (and exacerbated by user-mode container runtime TTY shims as, e.g., AWX uses by default). The stream swap-a-dee-doo we did previously works well enough for most Python code, but we were still getting nailed by problems with lower-level TTY inheritance when plugins use native libraries that splatter stuff willy-nilly on the TTY.

It sounds like you’ve got an explicit interactive use case that requires a shared TTY. We could probably do a var-settable config knob to disable the setsid call, so you could shut it off at task/block/role/play/host/group level for things that need it and still benefit from the isolation the rest of the time.

1 Like

Hacked this up and tried it against 1Password for Linux: seems to work.

2 Likes