cli icon indicating copy to clipboard operation
cli copied to clipboard

Store token in a more secure manner , support pairs of tokens

Open hadmut opened this issue 1 year ago • 7 comments

TL;DR

hcloud stores the tokens in plaintext in ~/.config/hcloud/cli.toml , where any other process, including any which reads from or includes files from random paths, making it too easy to steal the tokens.

Since hcloud enforces the use of these contexts stored in that file, and does not allow to pass the token per command (like, e.g. terraform/opentofu does) to the process without storing it, this is highly insecure and puts the tokens into high risk of beeing stolen, rendering the 2FA for the login to the Hetzner web console effectively useless.

Expected behavior

More secure handling of tokens, e.g.

  • ask for token during cli run without storing it
  • instead of storing the token in a file, store a command which is run and expected to output the token in stdout, thus allowing the usual Linux methods to store secrets in Wallets like Gnome Keyring or Keepass.
  • allow handling of a token pair of a read only and read/write token, where, e.g. the read only token with lower risk can be stored in plaintext in ~/.config/hcloud/cli.toml for fast and convenient read-only commands like hcloud server list, where aggressive commands like server create or server delete automatically choose the read/write token, which might be retrieved from a wallet then.

hadmut avatar Jul 05 '24 08:07 hadmut

Since hcloud enforces the use of these contexts stored in that file, and does not allow to pass the token per command (like, e.g. terraform/opentofu does) to the process without storing it.

You should be able to pass the token using the HCLOUD_TOKEN env var, without storing it in any configuration file, for example:

export HCLOUD_TOKEN="$(get-hcloud-token)"
hcloud server list

instead of storing the token in a file, store a command which is run and expected to output the token in stdout, thus allowing the usual Linux methods to store secrets in Wallets like Gnome Keyring or Keepass.

I think this is a good idea, not sure if a library already offers a way to do this on all the platforms that we support.

An alternative would be to provide a token_command configuration that will be triggered to retrieve the token from any vault/password manager you want.

allow handling of a token pair of a read only and read/write token, where, e.g. the read only token with lower risk can be stored in plaintext in ~/.config/hcloud/cli.toml for fast and convenient read-only commands like hcloud server list, where aggressive commands like server create or server delete automatically choose the read/write token, which might be retrieved from a wallet then.

The ideal solution would be to have fine-grained permissions on the tokens (per operations/per resources), but we are limited by what the API supports. You should already be able to store the read only token in a context, and use env var for the read/write tokens.

jooola avatar Jul 05 '24 09:07 jooola

Not a good idea to store secrets in environment, since environments can be read from other processes.

hadmut avatar Jul 05 '24 09:07 hadmut

Thanks for the suggestion. Do you have any tools where you like how this is implemented?

apricote avatar Jul 05 '24 10:07 apricote

e.g. mutt as a mail client or aws-cli for the Amazon Cloud do allow to configure to use the output of a subcommand as a credential

https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-configure.html https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-sourcing-external.html

Better solutions would require to modify the Hetzner API itself, e.g. use FIDO tokens or a TLS client certificate in a PKCS#11-Token or TPM for authentication.

There would be a lightweight solution which would not require to change the API, which is used by latest version of ssh client: Store the credential (here the API token) encrypted with gpg or a FIDO token in a file, and use gpg or the FIDO token to decrypt the credential inside the process, then use it as a regular token. This way you don't need to change the API, since all magic happens inside the client, but I am not really sure, how secure this is at the end of the day (and still depends on the client cli programm has not been tampered with).

https://developers.yubico.com/SSH/Securing_SSH_with_FIDO2.html

https://fido.ftsafe.com/open-ssh-with-fido-keys/

https://swjm.blog/the-complete-guide-to-ssh-with-fido2-security-keys-841063a04252

Maybe also have a look at Clevis/Tang and how they do store their secrets.

So there's mainly two options:

  • The big one with extending the Hetzner API allowing to use client certificates (including PKCS#11) or FIDO tokens, instead of a token, using PK cryptography

  • The small one using client side improvements only, and to use cryptography (PKCS#11, FIDO,...) to just store the token in an encrypted way or to call an external program to deliver the token through stdout (and handle all the tricky stuff in an external program).

Just calling some external program to fetch and output the token on stdout and read it from there might be by far the most flexible and easiest to implement way, because it allows to use any wallet or token, leaving it as the external program's problem how to do it.

hadmut avatar Jul 05 '24 10:07 hadmut

Unfortunately, although I would love to see some changes on how authentication and project management is handled in the API, we have to work with what we have right now. I think the easiest way to securely store tokens would be to use the host system's keyring. There are libraries that already handle this, for example https://github.com/zalando/go-keyring. Maybe we could also add support for calling third-party binaries. Although there the question would be, what would stop an attacker from just reading out the binary path from the config and then calling it too?

phm07 avatar Jul 11 '24 10:07 phm07

This issue has been marked as stale because it has not had recent activity. The bot will close the issue if no further action occurs.

github-actions[bot] avatar Oct 09 '24 12:10 github-actions[bot]

I think the easiest way to securely store tokens would be to use the host system's keyring. There are libraries that already handle this, for example https://github.com/zalando/go-keyring.

👍

Maybe we could also add support for calling third-party binaries. Although there the question would be, what would stop an attacker from just reading out the binary path from the config and then calling it too?

Nothing, but if said binary prompts the user for approval (such as requiring that a security key be tapped), I don't think that's really a problem.

A potentially-simpler solution than allowing a binary to be configured would be to allow the token to be provided by stdin (probably wrapped in a JSON object to leave your options open for the future).


Another suggestion that is more about preventing accidental token disclosure than actual security: on Linux and Mac, store the token in $XDG_STATE_HOME (defaulting to $HOME/.local/state) instead of $XDG_CONFIG_HOME. It's not uncommon for $XDG_CONFIG_HOME to be a git repository to allow configuration changes to be tracked. Technically you probably shouldn't even check for XDG_STATE_HOME on Mac, so I'd probably just always use ~/.local/state.

On Windows, the equivalent would be %LOCALAPPDATA% (\Users\<user>\AppData\Local).

ItsHarper avatar Sep 25 '25 05:09 ItsHarper