-
Notifications
You must be signed in to change notification settings - Fork 466
ACME External Account Binding #650
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
Conversation
Lines 154 to 171 in 0dd6564
Or we can just add another attribute to authority (here certificates/authority/authority.go Lines 35 to 73 in 0dd6564
acmeDB and just store the acmeDB there when you create it (using a setter method since you won't have access to the private variable from ca/ca.go). The admin handler already takes a *authority as its only argument. Then you'd just add a method like GetACMEDatabase (here certificates/authority/authority.go Lines 500 to 509 in 0dd6564
I'm fine with either method. The second may look a bit cleaner. |
I've chosen the first method, so that the |
Before this commit, EAB keys could be used CA-wide, meaning that an EAB credential could be used at any ACME provisioner. This commit changes that behavior, so that EAB credentials are now intended to be used with a specific ACME provisioner. I think that makes sense, because from the perspective of an ACME client the provisioner is like a distinct CA. Besides that this commit also includes the first tests for EAB. The logic for creating the EAB JWS as a client has been taken from github.com/mholt/acmez. This logic may be moved or otherwise sourced (i.e. from a vendor) as soon as the step client also (needs to) support(s) EAB with ACME.
This PR requires the changes from smallstep/linkedca#5 to be merged. |
@dopey: this PR now also includes tests for the Admin API (Admin, Provisioner, EAB). Thought it would be good to add those, so that new stuff can be added on top of the existing functionality. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments.
The ACME EAB keys are now also indexed by the provisioner. This solves part of the issue in which too many EAB keys may be in memory at a given time.
Although this may slow certain API calls down and may not be, strictly necessary, I think it's best to put all the ACME EAB operations behind RW locks to prevent concurrent updates to the DB and guarantee consistent result sets.
} | ||
|
||
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *acme.ExternalAccountKey) error { | ||
externalAccountKeyMutex.Lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we using mutexes? And if they are necessary will this be (yet another) a problem when running multiple open source CAs that read/write from the same DB?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See explanation about mutexes in the other comment.
Regarding running multiple open source CAs: using Badger will already stop you from doing that, as only a single process can work with the DB at a single time. When using MySQL, the mutexes are not going to save our day, because they're not distributed. We'd need a distributed locking mechanism built on top of Raft or Paxos, for example, to do so (using the current database interface). I guess the same "guarantees" apply as for HA ACME: it might just work. A (fully) distributed CA would be super interesting from an engineering perspective, though 😉
The above would probably be quite different if the DB layer provided full control over transactions (like explicit row locking) and a consistent view of the data within that transaction, for example through MVCC.
} | ||
|
||
func (db *DB) addEAKID(ctx context.Context, provisionerID, eakID string) error { | ||
referencesByProvisionerIndexMutex.Lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see that we might want the mutex here, but I want to understand why we need them for the other reads/writes/updates. Like if we're just doing a lookup by an ID. We don't generally use mutexes in other places for this (maybe we should be ?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added them in these other places out of an abundance of caution. For the ones in eakEADID
and removeEAKID
I have followed your pattern, because I thought they would certainly make sense there. For the others, I thought it wouldn't harm to add them there too.
When retrieving ACME EAB keys, of which there can be many, it can take a while before they're loaded and sent to the client. Considering that adding EAB keys may be scripted, it's likely that there will be multiple calls to adding new keys, perhaps mixed with retrievals. I wanted to have a strong guarantee that the results would always be consistent.
The various concrete nosql
implementations do (or at least seem to) offer transactions and provide means for a consistent view of the data (based on views), but since these implementations currently don't allow us to explicitly open and commit transactions for multiple types of entities in a single transaction, I try to mimic that using (rw) mutexes. Doing it at the DB API level may not be the best way to do it, but so far it worked quite well 😄. Ideally I would've liked direct control over when a DB transaction was started, the total unit of work to operate on, and then explicitly committed all at once.
acme/db/nosql/nosql.go
Outdated
certTable = []byte("acme_certs") | ||
certBySerialTable = []byte("acme_serial_certs_index") | ||
externalAccountKeyTable = []byte("acme_external_account_keys") | ||
externalAccountKeysByReferenceTable = []byte("acme_external_account_key_reference_index") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's an external_account_keyID by reference, right? You use keyID in the table and variable name below, but not in this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in bf21319.
In bf21319 I also fixed a bug with persistence. I must've missed this case in other manual tests due to using an existing ACME provisioner. It now seems to work correct doing multiple cycles of adding and removing keys with an entirely new provisioner. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
Description
This PR adds External Account Binding support to the ACME provisioner.
The implementation currently supports key material creation by using a new admin (beta) endpoint at
/admin/acme/eab
. Changes to the CLI can be found here.Still need to iron out some details and have to add tests, but the basics work with
acmez
already 🙂