Add keyAlgorithm and keySize fields to Certificates, and support ECDSA keys#722
Conversation
munnerz
left a comment
There was a problem hiding this comment.
This looks great - thanks very much for putting it together!
In order to make this universally compatible between Issuers, I think we should try and remove all distinction of RSA vs EC within the pkg/issuers directory, instead opting to refer to crypto.PrivateKey throughout.
As this touches a pretty critical area of the codebase and we are close to v0.4 already (should be released today or tomorrow), I think this will land in the v0.5 release provided we can get all these bits cleared up 😄 (in approx. 1mth!)
| KeySize int `json:"keySize,omitempty"` | ||
| // KeyAlgorithm is the private key algorithm of the corresponding private key | ||
| // for this certificate. Allowed values are either "rsa" or "ecdsa" | ||
| KeyAlgorithm string `json:"keyAlgorithm,omitempty"` |
There was a problem hiding this comment.
Can we create an 'enum' for these different possible values? It'd be worth also detailing as part of those that additional types may be added in future (so that anyone building API clients can handle unknown values properly).
ie.
type KeyAlgorithm string
const (
RSAKeyAlgorithm KeyAlgorithm = "rsa"
ECDSAKeyAlgorithm KeyAlgorithm = "ecdsa"
)
| switch strings.ToLower(crt.KeyAlgorithm) { | ||
| case "": | ||
| if crt.KeySize != 0 { | ||
| el = append(el, field.Invalid(fldPath.Child("keySize"), crt.KeySize, "should not be set if keyAlgorithm is not set")) |
There was a problem hiding this comment.
Why don't we allow specifying keySize without keyAlgorithm?
In future (i.e. once we add a proper Validating/MutatingWebhookConfiguration), we will apply defaulting to the keySize and keyAlgorithm fields which should allow us to handle this case better. But in the meantime it seems like we could allow a user to specify just keySize, and then we'll internally default the algorithm to RSA still (i.e. still validate that the specified size is a valid RSA keySize)
There was a problem hiding this comment.
@munnerz It's mostly to reduce confusion and make it explicit that if you do not want the defaults, you'll need to specify both fields (keySize, keyAlgorithm).
There was a problem hiding this comment.
I think a fair few people may just want to specify keySize without being explicit about the algorithm though. Despite us adding both of these fields in a single PR, they are not really linked (aside from how validation is performed).
I think we should allow setting just one or the other here (i.e. 'teach' validation about the defaults).
We can then handle proper defaulting with the mutating webhook for the next release, which should clean this bit of code up further 😄
| default: | ||
| el = append(el, field.Invalid(fldPath.Child("keyAlgorithm"), crt.KeyAlgorithm, "must be either empty or one of rsa or ecdsa")) | ||
| } | ||
|
|
There was a problem hiding this comment.
Can we also add a generic check for keySize being >0? (I know this case is already handled, but having it explicitly would be great 😄)
pkg/issuer/ca/issue.go
Outdated
| } | ||
|
|
||
| signerKey, err := kube.SecretTLSKey(c.secretsLister, c.issuerResourcesNamespace, c.issuer.GetSpec().CA.SecretName) | ||
| signerKey, err := kube.SecretRSAKey(c.secretsLister, c.issuerResourcesNamespace, c.issuer.GetSpec().CA.SecretName) |
There was a problem hiding this comment.
This key may not be a RSA key - we need to somehow handle this case. Can we use the generic crypto.PrivateKey (https://golang.org/pkg/crypto/#PrivateKey) throughout the codebase in order to handle this case better? It is more relevant now since #658 will allow users to begin building their own PKIs from scratch using cert-manager.
There was a problem hiding this comment.
Makes sense. I did that to ensure that stays as RSA. In hindsight, it was to not change too much of the codebase
I'll update the codebase to use crypto.PrivateKey throughout
pkg/issuer/vault/issue.go
Outdated
| signeeKey, err := kube.SecretRSAKey(v.secretsLister, crt.Namespace, crt.Spec.SecretName) | ||
| if k8sErrors.IsNotFound(err) || errors.IsInvalidData(err) { | ||
| signeeKey, err = pki.GenerateRSAPrivateKey(keyBitSize) | ||
| signeeKey, err = pki.GenerateRSAPrivateKey(pki.MinRSAKeySize) |
There was a problem hiding this comment.
Shouldn't this be using the new GeneratePrivateKeyForCertificate function?
|
/ok-to-test |
|
@munnerz Thanks for the PR review. I've made the necessary changes. |
|
The chart-verify job (which is poorly named!) is currently failing as you need to re-generate the reference documentation due to changing the API types. Can you try running |
|
My bad, sorry I missed that 😐. I've run the script and pushed the doc updates |
|
Just re-surfacing this comment as github is hiding it from us! I think a fair few people may just want to specify keySize without being explicit about the algorithm though. Despite us adding both of these fields in a single PR, they are not really linked (aside from how validation is performed). I think we should allow setting just one or the other here (i.e. 'teach' validation about the defaults). We can then handle proper defaulting with the mutating webhook for the next release, which should clean this bit of code up further 😄 |
|
Here's my understanding of your comments to ensure we're on the same page:
Are we on the same page 💯 😊 ? |
|
@munnerz I've made the necessary changes |
|
/retest |
munnerz
left a comment
There was a problem hiding this comment.
This is looking great 😄
Just a few more comments, but nothing major.
I need to actually pull and build a copy of this next, before merging as it changes a few important parts.
Thanks for all the work 🎉 looking forward to getting this in the next release.
| KeySize: 2048, | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Can we add a few more valid success cases? (e.g. for ecdsa with a valid size, explicit rsa with a valid size)
pkg/issuer/acme/issue.go
Outdated
| key, err := kube.SecretRSAKey(a.secretsLister, crt.Namespace, crt.Spec.SecretName) | ||
| if k8sErrors.IsNotFound(err) || errors.IsInvalidData(err) { | ||
| key, err = pki.GenerateRSAPrivateKey(2048) | ||
| key, err = pki.GenerateRSAPrivateKey(pki.MinRSAKeySize) |
There was a problem hiding this comment.
Should this be GeneratePrivateKeyForCertificate?
There was a problem hiding this comment.
& a change to use PublicKeyForPrivateKey and EncodePrivateKey
There was a problem hiding this comment.
I actually left that there specifically as acme is rsa only at the moment. You're right though, it does make sense to use GeneratePrivateKeyForCertificate in that block so when acme protocol supports ecdsa, there'll be no change needed.
| return rsaPrivateKey, nil | ||
| } | ||
|
|
||
| func SecretRSAKey(secretLister corelisters.SecretLister, namespace, name string) (*rsa.PrivateKey, error) { |
There was a problem hiding this comment.
nit: comment on exported function
pkg/util/pki/generate.go
Outdated
| 224: elliptic.P224(), | ||
| 256: elliptic.P256(), | ||
| 384: elliptic.P384(), | ||
| 521: elliptic.P521(), |
There was a problem hiding this comment.
Is it okay to reuse these (from a security perspective)
Also are they thread-safe? We run 5 Certificate workers, so there's a these could be used in parallel.
There was a problem hiding this comment.
Is it okay to reuse these (from a security perspective)
From the go source https://golang.org/src/crypto/elliptic/elliptic.go?s=11343:11360#L367, this is calculated once and cached. Just to ensure I didn't miss anything, I've changed it from using a map to calculating the curve when needed.
Also are they thread-safe? We run 5 Certificate workers, so there's a these could be used in parallel.
Afaik, in go, its ok to have concurrent reads on a map with no writer. I've removed the map implementation so this shouldn't be a problem anymore
| return nil, errors.NewInvalidData("error parsing rsa private key: %s", err.Error()) | ||
| } | ||
|
|
||
| err = key.Validate() |
There was a problem hiding this comment.
Is there a key.Validate equivalent for ecdsa? I see https://golang.org/pkg/crypto/ecdsa/#Verify, but not sure if that is for verifying a certificate against a public key?
There was a problem hiding this comment.
Unfortunately, there's no equivalent for ecdsa.
You're right as thats for verifying a certificate against the public key
|
Also don't worry too much about individual e2e job failures. There are a few known flakes in our e2e infrastructure that I am (gradually) working on fixing. The retest bot will take care of automatically retesting when the lgtm and approved label are applied 😄 |
- This PR adds two fields to CertificateSpec:
- `keyAlgorithm`, denotes which algorithm to use when generating
a private key. Can be either `rsa` or `ecdsa`. When not set, the
default algorithm used `rsa`.
- `keySize`, denotes the key size of the private key being generated.
For `rsa`, minimum key size is 2048 and maximum is 8192.
For `ecdsa`, sizes 224, 256, 384 & 521 are supported.
See https://golang.org/pkg/crypto/elliptic
- `keySize` can be set without being explicit about `keyAlgorithm`.
- If `keySize` is specified and `keyAlgorithm` is not provided, `rsa` will
be used as the key algorithm.
- `keyAlgorithm` can be set without being explicit about `keySize`.
- If `keyAlgorithm` is specified and `keySize` is not provided, key size
key size of `256` will be used for `ecdsa` key algorithm and
key size of `2048` will be used for `rsa` key algorithm.
- helper functions in `pki` package now return crypto.PrivateKey
|
Thanks very much for all the hard work here! 🎉 Let's get this into master so we can get feedback from end-users 😄I've opened #735 to follow up some of the pieces around validation (i.e. ensuring only RSA Certificates are processed for ACME Issuers). /lgtm |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: munnerz The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
What this PR does / why we need it:
Adds two fields to CertificateSpec:
keyAlgorithm, denotes which algorithm to use when generatinga private key. Can be either
rsaorecdsa. When not set, thedefault algorithm used
rsa.keySize, denotes the key size of the private key being generated.For
rsa, minimum key size is 2048 and maximum is 8192.For
ecdsa, sizes 224, 256, 384 & 521 are supported.See https://golang.org/pkg/crypto/elliptic
If both fields are not populated,
rsawith key size2048is usedAdds
pki.GeneratePrivateKeyForCertificatefunction that takes in aCertificateand generates a private key based on thekeyAlgorithm&keySizefields in theCertificateSpecAdds helper functions to the
pkipackage to generate/parsersaandecdsaprivate keysWhich issue this PR fixes (optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)format, will close that issue when PR gets merged): fixes #Special notes for your reviewer:
Release note: