Skip to content

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

Description

@hadmut

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.

Activity

  1. jooola commented on Jul 5, 2024

    @jooola
    Member

    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.

  2. hadmut commented on Jul 5, 2024

    @hadmut
    Author

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

  3. apricote commented on Jul 5, 2024

    @apricote
    Member

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

  4. hadmut commented on Jul 5, 2024

    @hadmut
    Author

    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.

  5. phm07 commented on Jul 11, 2024

    @phm07
    Contributor

    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?

  6. github-actions commented on Oct 9, 2024

    @github-actions
    Contributor

    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.

  7. ItsHarper commented on Sep 25, 2025

    @ItsHarper

    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).

  8. netzego commented on Apr 4, 2026

    @netzego

    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 implement token_command is most flexible and simple solution. via token_command one 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.md did not mention HCLOUD_TOKEN to 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 read manual/hcloud_config.md or 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

  9. phm07 commented on May 7, 2026

    @phm07
    Contributor

    there 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 .deb and .rpm files that also include manpages.

    As for token storage, yes, I agree, token_command is probably the best approach and least invasive. I will note it and have a look at it later.

  10. hadmut commented on May 12, 2026

    @hadmut
    Author

    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.

  11. netzego commented on May 13, 2026

    @netzego

    If we have token_password, one can run hcloud --config write.toml to 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.cred
    

    set token_password in cli.toml

    token_password = CREDENTIALS_DIRECTORY=~/.config/systemd/credentials/ systemd-creds --user decrypt --name=hcloud_token hlcoud_token.cred
    

    or 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"
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions