Show HN: Cryptic – A sensible secret management toolkit and Go library
github.com
github.com
I've become a big fan of Credstash over the last few months largely because it solves (well, more like makes irrelevant, I wouldn't really strongly say it's "solved") this problem through using AWS as a trusted third party. Further, Credstash actually handles key ACLs in a much, much smarter way in that it uses KMS encryption contexts, which can be dropped into an IAM policy's Condition block, to further restrict access to secrets. Cryptic doesn't seem to offer this, and its use of Redis as a backing store (itself intended to be used in trusted environments) makes me worried, too.
If continuing along with this project--and please don't take this as discouraging, it seems largely reasonable as a thing!--I would look in more depth at how Credstash uses KMS and bloodily rip it off to improve Cryptic's KMS functionality; it's very, very good at what it's doing.
This was largely a side project that's gone quite well so far(!) - it's something I'll continue to add to. Any other suggestions are more than welcome.
Like it says in the readme, I just wanted a simple (single binary) deployment - it's in no way a dig at credstash.
For example, with S3, I have been defining IAM policies that restrict each role's access to just the secrets granted to that role, with a prefix-conditional IAM statement. You can further define similar parameterized policies for IAM users and groups without even enumerating their names.
You're doing it at the S3 object level (presumably with a single KMS key?). Credstash does it at the KMS access level. Both are roughly equivalent, but Dynamo accesses are a lot cheaper in my neck of the woods than S3 accesses.
The Credstash documentation and code lead me to believe you are responsible for managing your own IAM policies - it won't set the policies in credential writer mode. Is that not correct? I expect this functionality to be part of the secret manager.
Sorry, I wasn't clear. I meant "cheaper" in terms of time. I have a lot of machines in flight at any time, and I do actively roll credentials--chef-zero runs every five minutes and pulls credentials out of Credstash. S3 accesses add latency, which slows my converges (sometimes by a lot; I currently have around ~25 secret lookups in one particular Chef run).
> I expect this functionality to be part of the secret manager.
I'm not saying you're wrong to want that, but I'm very skeptical of it--extremely skeptical. I can't envision a situation where I'd rather have policy management as part of the secret manager as opposed to part of my provisioning infrastructure. My KMS accesses are in IAM roles; there is no situation in which I would ever want Credstash to manage those, because I have other stuff in them! I build my policies in CloudFormation, just like I do everything else.
If anything, my biggest criticism of Credstash is that it has a hardcoded table name that I'd rather was an environment variable so that I could establish that via CloudFormation and dump it into an env var via Chef.
Would you agree that the secret manager should at least print out a suggested IAM policy for you to use? IAM policies are complex, tricky, and easy to mess up. I think it should absolutely be the job of the secret manager to at least sanity check your policies, if not write them directly.
> S3 accesses add latency [vs dynamodb], which slows my converges
That's useful to know, thanks.
(I also wouldn't be able to use any policy spat out by the tool in the first place--I use a Cfer, Ruby DSL, to generate CloudFormation and all I'd be doing is transliterating it over.)
Then you are an elite AWS operator, but you also have to think about how to teach and review work from other, less skilled operators working with you. In my experience it's not easy at all to do that with something as critical as IAM, so when I'm orchestrating resources, I heavily rely on IAM policy automation and built-in sanity checks.
def kms_read_policy(contexts)
contexts.map do |context|
{
PolicyName: "kms-read",
PolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: 'kms:Decrypt' ,
Resource: [
Cfer.cfize('arn:aws:kms:C{AWS.region}:' \
'C{AWS.account_id}:key/C{Fn.ref(:CredstashKey)}')
],
Condition: {
StringEquals:
context.map do |k, v|
[ "kms:EncryptionContext:#{k}", v ]
end.to_h
}
}
]
}
}
end.to_a
end
And a single DynamoDB policy in a managed policy that allows for querying the `credential-store` table. This just is not rocket science. It's helped by cfer[0] and cfer-provisioning[1] being pretty great, but it's very achievable.[0] - https://github.com/seanedwards/cfer
[1] - https://github.com/seanedwards/cfer-provisioning (I'm a maintainer)
Although, the only thing I can see it doing wrong is the use of passwords as keys https://github.com/domodwyer/cryptic/blob/6bd92fab6778dac26c... which is still an open issue in cryptopasta https://github.com/gtank/cryptopasta/issues/7
PBKDF2 incoming.
A common design pattern is to use KDF keys as key-encrypting keys wrapping fully random keys. The system in steady-state relies only on the fully random key; the KDF key, depending on the design, can sometimes be kept offline. This is how SSH and GPG handle keys: note how the actual keys the system uses are fully random and totally out of the user's hands.
But you might not need any of this mechanism at all; it might be totally possible to design this system such that keys are simply blobs, generated via urandom by reading crypt.Rand.
The only clue is the example SQL for creating the table appears to be MySQL. Is Postgres supported?
The underlying SQL uses "?" as a parameter binding placeholder, Postgres uses $1, $2 right?
Seems like an obvious oversight, I can put up a build if you're willing to test it?
I've been working on a similar tool in Rust: https://github.com/ketralnis/secrets. My focus has been less APIs and more person-to-person (e.g. sharing the team's twitter password between humans that need to use it) but I'd love to hear how you've solved some of the conceptual problems that I've struggled with in trying to make it secure.
Some issues that I've struggled with:
1. withstanding attacks on the file format, some of which are described in this paper: https://www.cs.ox.ac.uk/files/6487/pwvault.pdf. My answer has been the logging system that I talk about in Auditing below but I'd love to hear how you withstand some of these attacks in your system where it looks like you don't even control the file format (is that mysql?)
2. bootstrapping trust. how do I know I haven't been MitM'd, and how can I avoid having to trust the server? My system has a public/private key for every user, so when I share a password with you I'm encrypting it to your public key. The worst possible case then is that I inject my public key claiming to be you because then I get the passwords instead of you. Preventing this while also allowing for public keys to be rotated after a compromise of a client machine is currently stumping me. My current solution is to keep a record of every public key I've seen before, when I see a new one prompt the user to double check it out-of-band (like SSH does), and to just refuse to ever use a new public key for a user that I've seen before. But I bet that users don't actually check this, just like with SSH. And ideally I wouldn't rely on less secure out-of-band communication channels.
3. And speaking of verification, here's how I'm asking users if everything looks right:
=== secrets.vm ===
common name: secrets.vm
fingerprint: b957e10c998faa9909cff3ba4ec35485d04708c3ecc7481fe14d7f07bc0229cd
public key: c15e697e4807793ef8a9461a7b2c6cf2266d1ec1480a594e83b54e7b75e07702
public sign: f1db594eb55fe97657c57f2aa01afd1210a46d42d80d5552ac4d548162d4968e
mnemonic: AM ROBE KIT OMEN BATE ICY TROY RON WHAT HIP OMIT SUP LID CLAY AVER LEAR CAVE REEL CAN PAM FAN LUND RIFT ACME
does that look right? [y/n]
AFAICT these word-based (it's rfc1751) or sentence based (as recommended by https://www.usenix.org/conference/usenixsecurity16/technical...) are the best we have but do users actually verify these? How do you help your users prove that your server is really your server before they store secrets in it, in a way that they'll actually verify?4. Auditing. How can I, as an end user or as an admin of the server, prove that the server hasn't been tampered with to inject rogue keys? Right now I have a sketch for a merkle tree type log system where the server (1) logs every action (2) links every "fact" (a key, a secret, a user) to an entry in the log and (3) signs every log entry along with the log entry before it. Then a user can reconstruct the current state of the database to make sure that it matches. The upside is that it's pretty hard to forge, but the downside is that it's prohibitively expensive to verify: you really have to check the whole thing so you don't want that happening every time you retrieve a secret. And there are some tricky bits like making sure that the log doesn't have orphan entries that are easy to get wrong. Do you have a good solution to the auditing problem?
5. Keeping the crypto itself simple and auditable. I use libnacl for the actual crypto and openssl for TLS. I've found that simply writing down every way the crypto is used (https://github.com/ketralnis/secrets/blob/master/DESIGN.md) that I've found a lot of cases where it was too complicated and likely to be messed up. Finding that out made a big difference to me in how I write it: now I describe it first and then write the code and that has made a huge difference in keeping it simple. I don't see really any documentation on your crypto, how do you know you're doing it right?
And/or you could use some form of Web of trust model (one could say that a CA system is just a very small, highly trusted wot - typically one key with ultimate trust, that signs all valid certs - as opposed to wot that typically scores a key based on signatures by other (semi-)trusted/known keys).
Unfortunately Ca/wot/certs doesn't solve the problem - but it reduces "verify all the keys" and "rotate all the keys" to verify/rotate the key (eg: the key that signs your user cert - if you have a cert, you have a xhain of trust). It does however bring with it "maintain a CA" - which really is less work than "synchronise all the various public keys" but frequently ends up being hard to decentralise.
The latter is probably solved by realising better is better and having a few people/principals with signing powers (many priest, not just one bishop) and an audit trail, roll-call/revoke - can be a lot safer than ad-hoc trust-on-first-use.
Wouldn't validity and principals be best left to something like LDAP?
I still haven't found anything that fits in the gap between "I only have one machine" and "I'm comfortable setting up Active Directory et-al". I like reading about all these systems but I've only got 5 or so machines under my direct control (although something that integrates with AWS would be nice for when I'm doing development work there)
Yes and no. Even with easier access to caching etc, ldap can still be rather fragile (no (readable) ldap; no login)[1]
In general certificates need to be (proof of) principals (the entity that can prove ownership of the secret key X' corresponding to public key X" is X - where X is a uid (eg: e12e) Or a machine or service (login.example.com).
But if your use case is simple, and some meta-data can be readily attached to the certificate, it's easy to add limits on where key X is valid from (source ip/host), have access to (services) and when (time of day) -- and adding an expiry. If policy is max lifetime of a week except for CA keys, then that greatly simplifies revocation lists.
Generally "trust only this one CA" is simple, and allows moving capability (largely) to certificates. Centralised, on-line catalogs like LDAP make some things simpler.
And there might be a balance: perhaps some basic group info is in the cert - and each service can base policy on that (email login ok 24/7 - certain other services only 8-16 -- although I can't think of a compelling need for the latter right now).
[1] Of course one should check for revocations, but with an automated system to mint certificates, one can trade off with hour/day/week lifetimes. There was a recentish show hn: sharkey:
I will try to digest a little bit for people who aren't familiar with Go and by any chance read the project's README
- The examples you see there with commands like `./put ...` `./get ...` are actual Go command line apps found in the `cmd` directory.
What I mean is there are two command line applications which you actually might need to build separately one named `put` the other `get` . What they do, oh! well , never mind it should be crypting stuffs.
After being tasked to do devops for like 6 months. I understand the pain of having more secrets than those of the secret agencies we see on the movies.
Something that turn out to be true most of the times is, secrets should always be secrets. Managing secrets is supposed to be a secret.
I find the Configuration on the README to be full of secrets I mean passwords , API keys e.t.c. And wondering if cryptic is aware of that and how it is going to address this.
With my little understanding about security. I have a feeling that In some cases encryption is mistaken with secure.
I would highly recommend you read up on this thing called DevOps and what it is about. Start here:https://en.wikipedia.org/wiki/DevOps. You don't just "do devops".