diff --git a/tsa/howto/puppet.mdwn b/tsa/howto/puppet.mdwn index 5e865b055482bc781dd09844e040340273cb0f07..e42e22770755f9333cc29e8748f1d824203a2dee 100644 --- a/tsa/howto/puppet.mdwn +++ b/tsa/howto/puppet.mdwn @@ -643,3 +643,93 @@ resources with `troodi` in the name: Keep in mind that there are [automatic tags](https://puppet.com/docs/puppet/6.4/lang_tags.html) in exported resources which can complicate things. + +## Password management + +If you need to set a password in a manifest, there are special +functions to handle this. We do not want to store passwords directly +in Puppet source code, for various reasons: it is hard to erase +because code is stored in git, but also, ultimately, we want to +publish that source code publicly. + +We have two mechanisms on how to do this now: a HKDF to generate +passwords by hashing a common secret, and Trocla, which is currently +[in testing][]. + +[in testing]: https://trac.torproject.org/projects/tor/ticket/30009 + +### hkdf + +Most passwords in Puppet are managed through a [Key Derivation +Function][] (KDF), more specifically a [hash-based KDF][] that takes a +secret stored on the Puppet master (in `/etc/puppet/secret`) +concatenates this with a unique token picked by the caller, and +generates a secret unique to that token. An example: + +[hash-based KDF]: https://en.wikipedia.org/wiki/HKDF +[Key Derivation Function]: https://en.wikipedia.org/wiki/Key_derivation_function + + $secret = hkdf('/etc/puppet/secret', "dip-${::hostname}-base-secret") + +This generates a unique passwords for the given token. The password is +then used, in clear text, by the puppet client as appropriate. + +The function is an implementation of [RFC5869][], a [SHA256][]-based +HKDF taken from an earlier version of [John Downey's Rubygems +implementation][]. + +[John Downey's Rubygems implementation]: https://rubygems.org/gems/hkdf +[RFC5869]: https://tools.ietf.org/html/rfc5869 +[SHA256]: https://en.wikipedia.org/wiki/SHA-2 + +### Trocla + +[Trocla][] is another password-management solution that takes another +approach. With Trocla, each password is generated on the fly from a +secure entropy source ([Ruby's SecureRandom module][]) and stored +inside a state file (in `/var/lib/trocla/trocla_data.yml`, configured +`/etc/puppet/troclarc.yaml`) on the Puppet master. + +Trocla can return "hashed" versions of the passwords, so that the +plain text password is never visible from the client. The plain text +can still be stored on the Puppet master, or it can be deleted once +it's been transmitted to the user or another password manager. This +makes it possible to have Trocla not keep any secret at all. + +[Ruby's SecureRandom module]: https://ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/rdoc/SecureRandom.html +[Trocla]: https://github.com/duritong/trocla + +This piece of code will generate a [bcrypt][]-hashed password for the +Grafana admin, for example: + + $grafana_admin_password = trocla('grafana_admin_password', 'bcrypt') + +The plaintext for that password will never leave the Puppet master. it +will still be stored on the Puppet master, and you can see the value +with: + + trocla get grafana_admin_password plain + +... on the commandline. + +[bcrypt]: https://en.wikipedia.org/wiki/Bcrypt + +A password can also be set with this command: + + trocla set grafana_guest_password plain + +Note that this might *erase* other formats for this password, although +those will get regenerated as needed. + +Also note that `trocla get` will fail if the particular password or +format requested does not exist. For example, say you generate a +plaintext password with and then get the `bcrypt` version: + + trocla create test plain + trocla get test bcrypt + +This will return the empty string instead of the hashed +version. Instead, use `trocla create` to generate that password. In +general, it's safe to use `trocla create` as it will reuse existing +password. It's actually how the `trocla()` function behaves in Puppet +as well.