Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Security: open call for thoughts on securing WTF's config file #517

Closed
senorprogrammer opened this issue Jul 24, 2019 · 35 comments
Closed

Comments

@senorprogrammer
Copy link
Collaborator

WTF's config.yml file is a plain text file stored in the user's home directory. Given the nature of many of the modules in WTF, that file could hold account information, passwords, URIs, and API keys for the user's critical systems.

This issue is a place to discuss the security of that file.

  1. Does it need to be secured? Should we just trust the machine instead?
  2. If it should, how so? What's least-friction for the user?
@Seanstoppable
Copy link
Collaborator

This is interesting. Definitely something to think about. A bit harder since we support multiple file systems, and

Something low effort would be just checking file permissions and making sure the file is not readable for anyone except the user (0600 on *nix filesystems), similar to how ssh keys fail.

For more security conscious people, we could also support this like a plugin. I've been looking at a program called 'chezmoi' for handling my dot files, largely because of secret management https://github.com/twpayne/chezmoi/blob/master/docs/HOWTO.md#Keep-data-private, and it's methodology seems somewhat appropriate here.
Since chezmoi is written in golang, we could probably even just use it as a library.

@senorprogrammer
Copy link
Collaborator Author

senorprogrammer commented Jul 25, 2019

SSH keys are a good point. This file doesn't likely need to be more secure than anyone's private keys. I like the 0600 idea as a start.

@senorprogrammer
Copy link
Collaborator Author

#518 has been merged in. Ensures the default config is chmod 0600.

@senorprogrammer
Copy link
Collaborator Author

From a HN comment: "Without that you can't share your configs or have your config in your dotfiles-repository."

Not sure I buy the config sharing argument, but the dotfiles repo one is very valid.

@mide
Copy link

mide commented Aug 28, 2019

I would also like to see some concept of secrets management; I am not a huge fan of having my API keys just resident in a file on my computer.

Maybe if you could specify environment variables in the config, that way a user could use their own secrets management scheme.

...
    github:
      apiKey:
        env: GITHUB_API_KEY
      baseURL: ""
      enabled: true
      enableStatus: true
      position:
        top: 1
        left: 2
        height: 3
        width: 2
      refreshInterval: 300
...

And then they can invoke by doing something like:

GITHUB_API_KEY=$(cat /my/secret_file.txt) wtfutil

or

GITHUB_API_KEY=$(aws ssm get-parameter --name /util/wtfutil/github_api) wtfutil

or

GITHUB_API_KEY=$(op get item GitHub | jq '.details.fields[] | select(.designation=="api_key").value') wtfutil

Now, does it really matter? That means I'd still have my secret accessible at runtime. I'd have to have my AWS key and secret or whatever else, which would be no different from having in this file. Maybe flexibility is worthwhile for people.

@maweki
Copy link

maweki commented Aug 28, 2019

Since it came up in the discussion on hacker news (https://news.ycombinator.com/item?id=20817101):

I have all my config files in a repository (https://github.com/maweki/dotfiles) that I bring out into the system using stow (https://www.gnu.org/software/stow/). I only copy a few password and key files manually to a new pc. All configuration is kept up-to-date through the stow-ed symlinks into the dotfiles repository. This way of handling it makes it impossible to, for example, use the github module as the api key needs to be in that file and I obviously don't want to push the key to my dotfiles repo.

I think a good way to handle this would be the way home-assistant does it (https://www.home-assistant.io/docs/configuration/secrets/) where you give secrets with a special prefix and they are loaded from a secrets file.

The secrets file can be protected separately using git-secret or any other option to protect/encrypt single files.

The goal would be to have no private information in the main configuration file so that it can be shared without modification.

@senorprogrammer
Copy link
Collaborator Author

Someone opened a CVE on the file perms: https://nvd.nist.gov/vuln/detail/CVE-2019-15716

Note that this issue is already fixed in v0.20.0, released last week.

@senorprogrammer
Copy link
Collaborator Author

After first pass, I like the look of how Hass is doing things. Supporting an external secrets.yml file could be a relatively straight-forward first step.

@senorprogrammer
Copy link
Collaborator Author

@mide Modules that take an apiKey currently support the use of env vars to set that value. Unfortunately the docs don't currently document this capability so to figure out what they need to be called, you'll need to look into each module's settings.go source.

Here's the GitHub source: https://github.com/wtfutil/wtf/blob/master/modules/github/settings.go#L37

That said, it would be preferable for the user to be able to define the name of their env var and tell WTF what it is, rather than how it is now.

@OJFord
Copy link

OJFord commented Aug 29, 2019

I like the idea of kubernetes-ish sub-keys as env above, since it allows other options:

module:
    secret1:
        env: MY_SECRET
    secret2:
        file: /keybase/team/myteam/module-secret2
    secret3:
        cmd: aws secretsmanager get-secret-value --secret-id=module-secret3
    secret4: hunter2

@senorprogrammer senorprogrammer pinned this issue Aug 30, 2019
@senorprogrammer
Copy link
Collaborator Author

For API keys, all modules that take an API key setting also support setting that value via env var. The docs for all of them have now been updated to reflect this.

Examples:

@noxer
Copy link
Contributor

noxer commented Sep 10, 2019

Maybe using https://github.com/zalando/go-keyring is an option on desktop OS.

@alexfornuto
Copy link
Contributor

In #692 I've started the task of creating a secrets file, but am in over my head on Golang programming. collaboration is welcome.

@sam-github
Copy link
Collaborator

Anybody watching this issue because they want the feature, take a look at #869 to see if meets your need. Its workable now (modulo docs and that kind of nicety, and I hacked a dozen modules to use it, but not all of them). Feel free to ask any questions in the PR if you run into issues installing the credential management apps, or if I didn't add support to your favorite module, ping me, I will.

@paul-nameless
Copy link

paul-nameless commented May 1, 2020

Proposition: why not use backticks also known as as "command substitution".
password: `pass show wtf/secret-pass`
String inside of backticks will be executed, similar approach is used on a lot of other projects (msmtp, offlineimap, etc)

@sam-github
Copy link
Collaborator

It would have to be pass show wtf/secret-pass | head -n 1, or echo http://api.service.com | docker-credential-keychain get | jq -r .Secret.

It is definitely flexible, and doesn't require any module changes. Those are pluses.

On the negative side, it is putting a lot of scripting into the user's lap. Also, the scripting isn't portable to Windows (which I don't use, but I feel sympathy for those who do).

With the approach in #869, the only thing needed is to add the passwords (using the wtfutil CLI, so no complex scripting), and to delete the existing string from config. And to install the utilities, but that's required with either approach.

That said, #869 doesn't in any way prevent adding a feature to the yaml config parser to interpret shell commands as right-side values in yaml, both could exist simultaneously.

I'm curious, can you link to relevant secret handling docs for those projects?

@senorprogrammer
Copy link
Collaborator Author

I'm skeptical of having executable shell commands in configuration. Could you point to an example of where this is used to effect?

@paul-nameless
Copy link

Why are you skeptical about it?
Here is an example of some random dot file of vdirsyncer:
https://github.com/frnsys/dippindots/blob/75c36b9e4d909638369fe57e25e9557950abfceb/dots/calendar/vdirsyncer
And here is a .msmtprc configuration
https://marlam.de/msmtp/msmtp.html#passwordeval

They all use different methods, but the idea is the same. Backticks is just a proposition, can be use one of the existing approaches.

@zeonin
Copy link

zeonin commented May 2, 2020

Specifying arbitrary commands to provide a password is fairly common, especially with command line tools.

Bugwarrior (a utility to fetch actionable items from various sources and pull into the task management system, taskwarrior) allows for specifying passwords directly in the config file, retrieving them from the system keyring, prompting for them at runtime, and evaluating an arbitrary command: https://bugwarrior.readthedocs.io/en/latest/common_configuration.html#password-management

Mbsync (a utility to synchronize between a local maildir and remote IMAP server) allows specifying either a password directly in the config file with Pass ... or specifying a command with PassCmd "...": http://isync.sourceforge.net/mbsync.html

Another IMAP syncing utility (OfflineImap) allows for sourcing arbitrary python code for its config in general, and referencing those python commands to provide passwords: https://github.com/OfflineIMAP/offlineimap/blob/master/offlineimap.conf#L129
https://wiki.archlinux.org/index.php/OfflineIMAP#Using_GPG

Yet another (GetMail) allows running an arbitrary command to retrieve a password as well: https://wiki.archlinux.org/index.php/Getmail#Password_management

As mentioned, Vdirsyncer (which is used to sync CalDAV/CardDAV to a local directory) allows specifying the password in the config file, specifying a command to retrieve it, using the system keyring, and prompting for the password:
http://vdirsyncer.pimutils.org/en/stable/keyring.html

@senorprogrammer
Copy link
Collaborator Author

To be clear, skeptical does not mean opposed. It's not something I've seen before. Thanks for the examples @paul-nameless and @zeonin. It's an interesting idea.

@jonhadfield
Copy link
Contributor

I like the terraform style where the interpolation syntax is: ${}
You could follow the bcrypt example of: ${bcrypt(var.password, 12)} so that if you wanted the source to be MacOS Keychain, you could do something like:
token: ${keychain(weatherAPIKey)}
where a wtfUtil Keychain contains a credential called weatherAPIKey in plaintext
and a generic password version:
token: ${password("aGVsbG93b3JsZA==")}
where the value is a token string that's been encrypted using a password (and then base 64 encoded) that is prompted for on startup. The entered password would be hashed and compared with a hash kept in the file.
So a config could be something like:

wtf:
  password: c3VwZXJzZWNyZXQ=
  grid:
    columns: [100, 100]
    rows: [40, 40]
  mods:
    weather:
      position:
        top: 0
        left: 0
      apiToken: ${password("aGVsbG93b3JsZA==")}

Terraform config is using HCL so not sure how nicely this will work with YAML. Alternatively, jinja2 (used by Ansible with YAML) syntax is another option that would look and work in a similar way.

@OJFord
Copy link

OJFord commented May 2, 2020

I'm skeptical of having executable shell commands in configuration. Could you point to an example of where this is used to effect?

Slightly tongue-in-cheek answer: systemd

More serious, and very relevant answer: AWS's CLI, in obtaining credentials from an 'external process'. For example, mine is keybase fs read ....

@alexfornuto
Copy link
Contributor

Just my two cents:: #869 is not a solution to this issue for anyone who doesn't use Docker. I'm sure I'm not the only one who avoids using up resources in abstraction layers unless absolutely needed.

@sam-github
Copy link
Collaborator

@alexfornuto Not to pitch it too hard, the field is still open for other solutions, but just to be clear: you do understand that docker does not need to be installed?

The only dep is a single binary from https://github.com/docker/docker-credential-helpers/releases (which one depends on your keychain of choice).

@alexfornuto
Copy link
Contributor

Ahh, I was not aware of that. I've not used that software before, and when I saw Docker I instantly shuddered and didn't look further.

@OJFord
Copy link

OJFord commented May 17, 2020

It is not a solution if you don't want to use macOS keychain etc. though - such as in the example I posted above, the secret is something that already exists elsewhere, so I'd just like to be able to tell wtf how to read it; not duplicate it (and have to keep it in sync if it changes) in a second store.

Another option: make it dead simple to configure 'secrets providers', like:

secrets-providers:
  - name: keychain
    command: 'echo "$1" | jq ".Secret"'
  - name: keybase
    command: 'keybase fs read "/keybase/private/$(keybase whoami)/$1"'
  - name: pass
    command: 'pass show "$1" | head -n1'

Then:

foo:
  apikey:
    secret-provider: keychain / keybase / pass
    secret-name: foo-apikey

or with some kind of syntactic sugar:

foo:
  apikey: !SECRET(keychain, foo-apikey)

Benefit of this vs. arbitrary 'external commands' inline isn't just de-duplication - it'd allow distributing some common ones with wtf itself, making it easy for people to configure those or just use something if they don't really care, but also allow others to use custom (even proprietary) methods that aren't built-in.

@sam-github
Copy link
Collaborator

Note that on mac OS, you can also use docker-credential-pass, you aren't tied to keychain, or you can just write a shell script called docker-credential-keybase (or some more suitable name).

If you have immediate desire for pulling keys from keybase, your script only has to satisfy the "get" interface from https://github.com/docker/docker-credential-helpers#development: read the name of the server from stdin, then write a 3-item bit of JSON to stdout.

If you wanted to write a full keybase helper in go the credential-helper repo has some utility functions for building such a beast, maybe they'd even accept a contribution, keybase is pretty popular. Who knows what direction it will take post-zoom, though. I wish them well, I use it a bit myself.

In the longer term, I think the secret idea is interesting, give it a shot, The current helper approach didn't add any yml config file syntax, so the field is wide open still for some kind of generic shell script config-generation facility, or something more targeted at secrets.

@OJFord
Copy link

OJFord commented May 20, 2020

Another, slightly tangential, thought on this - if the external process could be used anywhere (not just in some special secret: key) then it would bring a lot of flexibility, such as dynamically rendering a repositories list for the GitHub module (solving #898) or dealing with system differences when using the same config on multiple hosts (e.g. enabled: !eval 'if [ "$(hostname)" = laptop ]; then echo true; else echo false; fi' for a battery/WiFi indicator or something).

@senorprogrammer
Copy link
Collaborator Author

The CmdRunner module provides the ability to run any external process and format the output however you like.

@OJFord
Copy link

OJFord commented May 20, 2020

Sure, but that's just for displaying the output, that doesn't allow it to be used for the value of enabled for example. In the case of repos you could use it instead of the github module, but they wouldn't be open-able; in my 'show battery module only if on laptop' example, there's no way to do that with CmdRunner without putting up with an empty module when not on laptop.

Anyway, it's a tangent, I just thought it was worth mentioning that there could be use cases for 'external process output as config value' beyond just secrets.

@senorprogrammer
Copy link
Collaborator Author

senorprogrammer commented May 20, 2020

This sounds like an edge case that would add considerable technical and conceptual complexity for very marginal value. This can be accomplished by having a separate config file.

This is also unrelated to the security issue. If you want to advocate for this, feel free to open an issue dedicated to it.

@viggy28
Copy link

viggy28 commented Jul 1, 2020

Saw a similar conversation on Twitter, and a saw suggestion of mac keychains. (I am not sure how that works and also specific to macos)

@sam-github
Copy link
Collaborator

the helper approach that got merged supports the mac keychain as a store

@panckreous
Copy link

What about the simplest approach - multiple config files. Going back to the dotfiles example near the top of this thread, the way this is generally handled is the general dotfiles, most configuration and no private info) are public and then local files, if they exist, are also sourced in at the end of each.

For example, the .zshrc file is public, then at the end there's a line like this [[ -r $HOME/.zshrc.local ]] && source $HOME/.zshrc.local - the local file (can be named anything) handles ssh keys, aws creds, etc - some new values, some that overwrite parts of the parent file - and can be kept anywhere privately.

maybe something like --config=config.yml,/some/where/else/config.private.yml ? The configs get merged together with the latter file taking precedence, and everyone can store their private config however they prefer.

@stale
Copy link

stale bot commented Jan 11, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.