twpayne / chezmoi

Manage your dotfiles across multiple diverse machines, securely.
https://www.chezmoi.io/
MIT License
13.36k stars 493 forks source link

Improve support for 1Password service accounts #3498

Closed jimmythomson closed 8 months ago

jimmythomson commented 9 months ago

Is your feature request related to a problem?

I'm using 1Password and have setup a service account for storing my various credentials. I've then setup the 1Password CLI as described here: https://developer.1password.com/docs/service-accounts/use-with-1password-cli/

That page suggests that the user stores their service account's token in the OP_SERVICE_ACCOUNT_TOKEN environment variable.

With OP_SERVICE_ACCOUNT_TOKEN defined, I then test the reading of an item with:

chezmoi execute-template '{{ onepasswordRead "op://my-vault/my-item/my-field" }}'

This fails as chezmoi tries to communicate with the 1Password app, which isn't installed on my system. As far as I understand it, the mere existence of the OP_SERVICE_ACCOUNT_TOKEN environment variable is enough for the op read to succeed. Indeed, if I run the following directly, then it succeeds:

op read  "op://my-vault/my-item/my-field"

I took a quick look at the code and have a work around, which is simply to export the environment variable as OP_SESSION_SERVICE_ACCOUNT_TOKEN. The code appears to look specifically for the OP_SESSION_ prefix. After having done this, then the chezmoi execute-template function succeeds.

Describe the solution you'd like

Ideally I'd like for chezmoi to automatically look for the OP_SERVICE_ACCOUNT_TOKEN environment variable. Perhaps even some documentation to describe the naming of the env var for service accounts would be enough (i.e. the importance of the OP_SESSION_ prefix).

Thanks

This is an amazing utility. Many thanks to all the contributors!

halostatue commented 9 months ago

I don't currently have a service account set up on either of my 1Password accounts, but I’ll look to see what I can see with this. It will not be today.

I think we might be able to switch modes if OP_SERVICE_ACCOUNT_TOKEN is defined, but I need to understand how service account works better so that I can figure out what it would do to our tests (I suspect that I’m going to have two .txtar files since the application would be called differently by the same command parameters).

Worst case, we may need to have separate template verbs for service account handling or 1Password Connect handling, because the current template verbs permit access from more than one account, and it doesn't look like service account can do that.

halostatue commented 9 months ago

@twpayne I have some thoughts on how to approach this, but wanted to discuss it first.

We should support both service accounts (OP_SERVICE_ACCOUNT_TOKEN) and connect (OP_CONNECT_HOST, OP_CONNECT_TOKEN). These are mutually exclusive modes of operation both with each other and with normal account usage.

Session accounts can use the template functions onepasssword, onepasswordDetailsFields, onepasswordDocument, onepasswordItemFields, and onepasswordRead. They cannot see one password accounts, use session tokens, or op login.

Connect can use the template functions onepassword, onepasswordDetailsFields, onepasswordItemFields, and onepasswordRead. They cannot access documents, see one password accounts, use session tokens, or op login.

This means that we either need to have separate functions (I am very 👎 on this, but there is one argument in favour of this) or determine which mode we are in. This would be (in order): connect, service account, normal.

In connect and service account modes, we would:

In connect mode, we would need to have onepasswordDocument trigger an error (completely incompatible).

This is going to take some time to build out in a way that it doesn't repeat the same code everywhere, but I don't really see any way to do this without having a few more switch statements. I would want to determine which mode we are in once (on first run of any onepassword* template function) so that the value is cached (it would be cheap to determine every time, since it is checking for values in 2–3 environment variables).

Thoughts?


The one argument in favour of separate functions is that each mode is tied to exactly one account and specific vault / secret views. Alternative functions would allow this to live side-by-side with normal 1Password where the "account" parameter would be the OP_SERVICE_ACCOUNT_TOKEN (or OP_CONNECT_HOST) and a new parameter would be OP_CONNECT_TOKEN (the presence of the fourth parameter would determine which is which). I don't really like this approach, but it may be an option.

twpayne commented 9 months ago

This means that we either need to have separate functions (I am very 👎 on this, but there is one argument in favour of this) or determine which mode we are in. This would be (in order): connect, service account, normal.

How about having the user explicitly specify the mode with a onepassword.mode configuration variable, instead of trying to automatically detect it from the OP_ environment variables? If possible, I would like to minimize the chezmoi's knowledge of 1Password's implementation details to reduce the coupling between the two projects.

@jimmythomson as a work-around for now you should be able to use

{{ output "op" "read" "op://my-vault/my-item/my-field" | trim }}

in your templates. chezmoi will export the OP_ environment variables. The fromJson template function might also be handy.

halostatue commented 9 months ago

How about having the user explicitly specify the mode with a onepassword.mode configuration variable, instead of trying to automatically detect it from the OP_ environment variables? If possible, I would like to minimize the chezmoi's knowledge of 1Password's implementation details to reduce the coupling between the two projects.

That will work, but I think we should "verify" mode correctness, as different commands on op work in different modes, which affects which template functions are available.

The verification would be:

We would still need to prevent the use of certain parameters (e.g., account) and certain template functions in non-account mode as they aren't compatible with those modes.

Would we want separate txtar files for these mode cases, or should we jam these into the current 1Password txtar?

twpayne commented 9 months ago

Let's do separate testscript files, as the functionalities are sufficiently separate.

halostatue commented 9 months ago

OK. I should be able to get to this on the weekend.

breiti commented 9 months ago

Wouldn't it be sufficient to just call op read without any options by default and let it decide what to do based on the environment?

twpayne commented 9 months ago

Wouldn't it be sufficient to just call op read without any options by default and let it decide what to do based on the environment?

My understanding is that this would be sufficient for a single 1Password account, but chezmoi also supports multiple 1Password accounts (e.g. one for personal secrets and a second for work secrets), so much of the complexity is ensuring op read has the right OP_ environment variables set for the account you want to retrieve the secret from.

@halostatue is the expert here, however.

halostatue commented 9 months ago

@twpayne is right. The 1Password URL for op read is op://VAULT/ENTRY/FIELD with nowhere to put the target account. If (like me), you have multiple accounts you need to do op read --account ACCOUNT op://VAULT/ENTRY/FIELD.

When 1Password / op are configured for biometric authentication, it’s easy. If not, then you need to do eval $(op signin --account A) and eval $(op signin --account B) before running chezmoi apply because different environment variables with the authentication token get set, and chezmoi needs to do some juggling to support the multiple account token setup. If you don't run eval $(op signin --account …) before, then chezmoi will helpfully run op signin --account A --raw and store the resulting token in an account map so that it can use the same authentication each time.

I guess that I’ve become the expert on 1Password/chezmoi integration, but most of what I did was to make Tom's original implementation work with version 2 (and it already did most of what I described above; it's just that everything changed with op version 2).

halostatue commented 9 months ago

I found this tool from FiloSottile/awesome-age, but it has an excellent discussion about the different modes where op runs: https://github.com/stevelr/age-op.

halostatue commented 9 months ago

@jimmythomson, can you test these changes?

I’ve just opened #3557 which should resolve this. Once the CI passes, there should be some test binaries available that you can use to test service automation support.

I do not have a service automation account set up (or 1Password Connect), but I believe that everything should work as expected and I think that the documentation should be clear on what is allowed and what should not work.