Repository navigation
Store token in a more secure manner , support pairs of tokens #808
Description
Activity
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_commandconfiguration 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.
Not a good idea to store secrets in environment, since environments can be read from other processes.
Thanks for the suggestion. Do you have any tools where you like how this is implemented?
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.htmlBetter 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.
-
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?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.
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_HOMEto be a git repository to allow configuration changes to be tracked. Technically you probably shouldn't even check forXDG_STATE_HOMEon Mac, so I'd probably just always use~/.local/state.On Windows, the equivalent would be
%LOCALAPPDATA%(\Users\<user>\AppData\Local).my two cents. while setting a function in your
.bashrc(see example below) works and minimize the exposure of permanent exported environment variables, the problem remains that this can be read from other processes. (but much better than storing your keys in plaintext). i think, the proposal from @jooola to implementtoken_commandis most flexible and simple solution. viatoken_commandone can configure any decent pw manager including fido/yubi keys etc. and it should work on any os.# hcloud function hc { # shellcheck disable=2068 HCLOUD_TOKEN="$(pass PASSFILE_HCLOUD_APIKEY)" hcloud $@ }
site note:
tutorials/setup-hcloud-cli.mddid not mentionHCLOUD_TOKENto prevent storing secrets in plaintext. i think, it would be a nice addition to make your user aware. there is also no man-page, so users have to find and readmanual/hcloud_config.mdor find this issue. (your docs are well but a man page would be much appreciated. for many this is the first place to look up such things.)edit: formating
Reacted by phm07there is also no man-page, so users have to find and read manual/hcloud_config.md or find this issue.
Note that since a few versions we include
.deband.rpmfiles that also include manpages.As for token storage, yes, I agree,
token_commandis probably the best approach and least invasive. I will note it and have a look at it later.If you implement token_command, please implement two commands: one for a read-only-token, and an optional one for a write-token for write access, since the latter needs to be kept more secure.
By the way, ubuntu 26.04 came with improved support for TPM and better systemd-creds to store credentials. It should be possible to use this with hcloud.
If we have
token_password, one can runhcloud --config write.tomlto configure a different "profile". IMO, this will do just fine. Btw, sd-creds are useful, but my use case is NOT a password-less setup. The master password manager should unlock the secret. (one can use sd-creds to unlock via her user pw, though. But it is more complicated..) For automated setups sd-creds are superb.create .cred file
systemd-creds encrypt --user --name=hcloud_token <(echo -n $(pass PASSFILE)) ~/.config/systemd/credentials/hcloud_token.credset token_password in cli.toml
token_password = CREDENTIALS_DIRECTORY=~/.config/systemd/credentials/ systemd-creds --user decrypt --name=hcloud_token hlcoud_token.credor for unattended runs set $HCLOUD_TOKEN in a .service file.
LoadCredential=hcloud_token:~/.config/systemd/credentials/hcloud_token.cred Environment="HCLOUD_TOKEN=%d/hcloud_token"
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.