-
Notifications
You must be signed in to change notification settings - Fork 56
Add option to sign multiple artifacts with the same key and certificate #645
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
Add option to sign multiple artifacts with the same key and certificate #645
Conversation
Signed-off-by: Maya Costantini <[email protected]>
ab8b38e
to
98181b5
Compare
Converting to draft for fixing CI |
Thanks @mayaCostantini! I'll have some time to review this later today. |
Based on Williams useful explanation in #514 (comment), should we consider:
|
We could go for 1., I didn't make it the default mode here just in case I failed to identify a potential use case for having one CSR per artifact. As for 2., we could leave the parameter in the |
Signed-off-by: Maya Costantini <[email protected]>
98181b5
to
3a0f6b7
Compare
I was thinking that for the API user the Signer argument is not really necessary as the caller can just create several Signers if they want several CSRs to be used (while still reusing the same identity token). However, looking at the API again I'm reminded the token is not a constructor argument to Signer, but an argument to I wonder if it it would be cleaner to have another signer class instead (like I think William may have suggested at one point), one that would take the token as a constructor argument and would cache the CSR? |
@mayaCostantini given that this changes the internal state of the
I'm okay with either of these (with a slight preference for the second), and IMO either would result in a cleaner diff/easier to maintain internal invariants. If we go the second route, we'll probably want the instance to be "self-maintaining" -- accessing the signing key and materials should always produce materials that are valid at the current time, so both should be properties that transparently refresh as needed under the hood (except for in the |
Oh, this is a good point -- I forgot that I had designed it this way... Yeah, a more general refactor is possibly in order here: There's some weird impedance mismatch there, though -- constructing a On the other hand: maybe that's okay? It's no different from the current |
I think I agree with this -- it makes sense for the signer to be tied to a single identity -- but also with your other observations in this comment... Maybe the easy/correct solution is a new signer-class that implements this slightly different concept? Naming is hard, so I have not come up with a good name for this SingleCertificateSigner. If it becomes clear that the new class provides everything users need, then the old one can be deprecated and removed later. |
These changes will land with 2.0, so I'm personally okay with a decent bit of breakage here! So long as we can come up with a suitable API 🙂 Just to write the constraints down in a single place:
One idea:
As an example: # access the production Sigstore instances
production = SigningContext.production()
with production.with_signer(identity_token=...) as signer:
result = signer.sign(...) Thoughts? I'm still not 100% happy with that (maybe it shouldn't be a context manager?), but it's food for thought 🙂 |
Thanks for your thoughts on this. I personally like the idea of a context manager; I find it good to make API users understand that resources associated to signing (key and certificate) are ephemeral and should remain within a certain scope to avoid mixing up identities. Does something like the following implement what you were thinking of @woodruffw ? from contextlib import contextmanager
# New `Signer` class
class Signer:
def __init__(self, identity_token: str, cache: bool=True):
self._identity_token: str = identity_token
self._signing_certificate: Optional[FulcioCertificateSigningResponse] = None
self._private_key: Optional[ec.EllipticCurvePrivateKey] = None
self.cache : bool = cache
@property
def private_key(self) -> ec.EllipticCurvePrivateKey:
# Get or generate a private key
...
@property
def signing_cert(
self,
) -> Callable[[str, ec.EllipticCurvePrivateKey], FulcioCertificateSigningResponse]:
# Get or request a signing certificate for Fulcio with the provided identity token
...
def sign(self, input_: IO[bytes]) -> SigningResult:
# Sign the artifact
...
class SigningContext:
def __init__(self, *, fulcio: FulcioClient, rekor: RekorClient):
...
def production(cls) -> SigningContext:
...
def staging(cls) -> SigningContext:
...
@contextmanager
def with_signer(self, identity_token: str):
signer = Signer(identity_token: str, cache=True)
try:
yield signer
finally:
del signer |
Yep, looks close to what I was thinking! We should think a bit more about the lifetimes here (maybe it makes sense for the new |
+1. We could add a |
I'm not directly opposed to that, although I think it produces an unidiomatic structure in end-users of the API: they'll end up wrapping the context manager in a Since we'll need to do the check on every signing operation anyways, I think we should encourage end-user usage like this: with ctx.with_signer(identity_token=..., cache=True):
try:
signer.sign(...)
except ExpiredIdentity:
...
except ExpiredCertificate:
# NOTE: Can only happen when cache=True
... |
Sounds good! Should I proceed with the implementation? |
Works for me! I assume you'll need an |
Signed-off-by: Maya Costantini <[email protected]>
Signed-off-by: Maya Costantini <[email protected]>
@woodruffw @jku feel free to take a look at ff30b09 |
Thanks! I'll take a look today. |
/gcbrun |
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, great work @mayaCostantini!
I'd like @di to also give this a review before merge.
Signed-off-by: William Woodruff <[email protected]>
/gcbrun |
Thanks for the thorough reviews and help @woodruffw! I am excited as well to see this in v2.0 🙂 |
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.
This looks good to me.
As a nit-pick, if we don't have a good use case for the CLI flag, maybe it just does not need to exist?
- API users can always handle their signers like they want
- CLI users can invoke sign multiple times if they really need individual certs (admittedly this approach currently has other downsides like repeated TUF initializations)
Agreed, removing. |
Signed-off-by: William Woodruff <[email protected]>
/gcbrun |
/gcbrun |
/gcbrun |
Thanks a ton @mayaCostantini! Excited to land this. |
Thanks a lot to you @woodruffw ! |
Summary
Resolves #514
Add a
--single-cert
option to thesign
subcommand to sign multiple artifacts by generating one private key and requesting a single certificate from Fulcio.Release Note
--single-cert
option to sign multiple artifacts with a single key and certificateDocumentation
Added corresponding documentation in
README.md
(sign
command output).