Pod Certificates: Add StubPKCS10Request; migrate in-tree usages to it

This commit is contained in:
Taahir Ahmed 2026-01-27 18:28:48 -08:00
parent 285222e92e
commit a86f377950
7 changed files with 327 additions and 88 deletions

View file

@ -340,8 +340,7 @@ type PodCertificateRequestSpec struct {
// seconds (1 hour). This constraint is enforced by kube-apiserver.
MaxExpirationSeconds *int32
// pkixPublicKey is the PKIX-serialized public key the signer will issue the
// certificate to.
// The PKIX-serialized public key the signer will issue the certificate to.
//
// The key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521,
// or ED25519. Note that this list may be expanded in the future.
@ -352,13 +351,20 @@ type PodCertificateRequestSpec struct {
// setting a status.conditions entry with a type of "Denied" and a reason of
// "UnsupportedKeyType". It may also suggest a key type that it does support
// in the message field.
//
// DEPRECATED: This field is replaced by StubPKCS10Request. If
// StubPKCS10Request is set, this field must be empty. Signer
// implementations should extract the public key from the StubPKCS10Request
// field.
//
// +optional
PKIXPublicKey []byte
// proofOfPossession proves that the requesting kubelet holds the private
// key corresponding to pkixPublicKey.
// A proof that the requesting kubelet holds the private key corresponding
// to pkixPublicKey.
//
// It is contructed by signing the ASCII bytes of the pod's UID using
// `PKIXPublicKey`.
// `pkixPublicKey`.
//
// kube-apiserver validates the proof of possession during creation of the
// PodCertificateRequest.
@ -374,8 +380,40 @@ type PodCertificateRequestSpec struct {
// If the key is an ED25519 key, the the signature is as described by the
// [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the
// golang library crypto/ed25519.Sign).
//
// DEPRECATED: This field is replaced by StubPKCS10Request. If
// StubPKCS10Request is set, this field must be empty.
//
// +optional
ProofOfPossession []byte
// A PKCS#10 certificate signing request (DER-serialized) generated by
// Kubelet using the subject private key.
//
// Most signer implementations will ignore the contents of the CSR except to
// extract the subject public key. The API server automatically verifies the
// CSR signature during admission, so the signer does not need to repeat the
// verification.
//
// The subject public key must be one of RSA3072, RSA4096, ECDSAP256,
// ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in
// the future.
//
// Signer implementations do not need to support all key types supported by
// kube-apiserver and kubelet. If a signer does not support the key type
// used for a given PodCertificateRequest, it must deny the request by
// setting a status.conditions entry with a type of "Denied" and a reason of
// "UnsupportedKeyType". It may also suggest a key type that it does support
// in the message field.
//
// Some CA implementations require that the client (the signer
// implementation, in this case) provide a PKCS#10 certificate signing
// request, even if the CA only extracts the subject public key from the
// request. To enable compatibility with these CAs, Kubelet will generate a
// stub PKCS#10 request that the signer implementation can then pass on to
// the CA.
StubPKCS10Request []byte
// unverifiedUserAnnotations allow pod authors to pass additional information to
// the signer implementation. Kubernetes does not restrict or validate this
// metadata in any way.
@ -485,6 +523,9 @@ const (
// MaxProofOfPossessionSize is the maximum size permitted for the
// ProofOfPossession field.
MaxProofOfPossessionSize = 10 * 1024
// MaxStubPKCS10RequestSize is the maximum size permitted for the
// UnverifiedPKCS10Request field.
MaxStubPKCS10RequestSize = 10 * 1024
// MaxCertificateChainSize is the maximum size permitted for the
// CertificateChain field.
//

View file

@ -646,6 +646,26 @@ func ValidatePodCertificateRequestCreate(req *certificates.PodCertificateRequest
}
}
// Either (PKIXPublicKey, ProofOfPossession) xor (StubPKCS10Request)
// must be set.
if len(req.Spec.StubPKCS10Request) != 0 && len(req.Spec.PKIXPublicKey) == 0 && len(req.Spec.ProofOfPossession) == 0 {
// Valid, using StubPKCS10Request
allErrors = append(allErrors, validateStubPKCS10Request(req)...)
return allErrors
} else if len(req.Spec.StubPKCS10Request) == 0 && len(req.Spec.PKIXPublicKey) != 0 && len(req.Spec.ProofOfPossession) != 0 {
// Valid, using PKIXPublicKey and ProofOfPossession
allErrors = append(allErrors, validateDeprecatedPKIXPublicKey(req)...)
return allErrors
} else {
// Invalid, any other combination.
allErrors = append(allErrors, field.Invalid(field.NewPath("spec"), field.OmitValueType{}, "exactly one of (stubPKCS10Request) or (pkixPublicKey, proofOfPossession) must be set"))
return allErrors
}
}
func validateDeprecatedPKIXPublicKey(req *certificates.PodCertificateRequest) field.ErrorList {
var allErrors field.ErrorList
if len(req.Spec.PKIXPublicKey) > certificates.MaxPKIXPublicKeySize {
allErrors = append(allErrors, field.TooLong(field.NewPath("spec", "pkixPublicKey"), req.Spec.PKIXPublicKey, certificates.MaxPKIXPublicKeySize))
return allErrors
@ -699,6 +719,85 @@ func ValidatePodCertificateRequestCreate(req *certificates.PodCertificateRequest
return allErrors
}
var (
oidExtensionSubjectAltName = []int{2, 5, 29, 17}
)
func validateStubPKCS10Request(req *certificates.PodCertificateRequest) field.ErrorList {
var allErrors field.ErrorList
if len(req.Spec.StubPKCS10Request) > certificates.MaxStubPKCS10RequestSize {
allErrors = append(allErrors, field.TooLong(pkcs10ReqPath, req.Spec.StubPKCS10Request, certificates.MaxStubPKCS10RequestSize))
return allErrors
}
pkcs10Req, err := x509.ParseCertificateRequest(req.Spec.StubPKCS10Request)
if err != nil {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "must be a valid PKCS#10 CSR"))
return allErrors
}
// Check key type and parameters
switch pkcs10Pub := pkcs10Req.PublicKey.(type) {
case ed25519.PublicKey:
// ed25519 has no key configuration to check
case *ecdsa.PublicKey:
if pkcs10Pub.Curve != elliptic.P256() && pkcs10Pub.Curve != elliptic.P384() && pkcs10Pub.Curve != elliptic.P521() {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, "curve "+pkcs10Pub.Curve.Params().Name, "elliptic public keys must use curve P256, P384, or P521"))
return allErrors
}
case *rsa.PublicKey:
if pkcs10Pub.Size()*8 != 3072 && pkcs10Pub.Size()*8 != 4096 {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, fmt.Sprintf("%d-bit modulus", pkcs10Pub.Size()*8), "RSA keys must have modulus size 3072 or 4096"))
return allErrors
}
default:
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "unknown public key type; supported types are Ed25519, ECDSA, and RSA"))
return allErrors
}
// Check that the request is empty, except for DNS or IP SANs.
if len(pkcs10Req.Subject.Country) != 0 ||
len(pkcs10Req.Subject.Organization) != 0 ||
len(pkcs10Req.Subject.OrganizationalUnit) != 0 ||
len(pkcs10Req.Subject.Locality) != 0 ||
len(pkcs10Req.Subject.Province) != 0 ||
len(pkcs10Req.Subject.StreetAddress) != 0 ||
len(pkcs10Req.Subject.PostalCode) != 0 ||
len(pkcs10Req.Subject.SerialNumber) != 0 ||
len(pkcs10Req.Subject.CommonName) != 0 ||
len(pkcs10Req.Subject.Names) != 0 {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "PKCS#10 request must have empty subject"))
return allErrors
}
if len(pkcs10Req.EmailAddresses) != 0 {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "PKCS#10 request must not contain EmailAddress subject alternate names"))
return allErrors
}
if len(pkcs10Req.URIs) != 0 {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "PKCS#10 request must not contain URI subject alternate names"))
return allErrors
}
for i := range pkcs10Req.Extensions {
if pkcs10Req.Extensions[i].Id.Equal(oidExtensionSubjectAltName) {
// If the SubjectAlternateName contained non-IP or non-DNS content,
// then EmailAddresses or URIs would have been non-empty above.
} else {
// All other extensions are forbidden.
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "PKCS#10 request may not contain arbitrary extensions"))
return allErrors
}
}
if err := pkcs10Req.CheckSignature(); err != nil {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "invalid signature"))
return allErrors
}
return allErrors
}
func hashBytes(in []byte) []byte {
out := sha256.Sum256(in)
return out[:]
@ -707,6 +806,7 @@ func hashBytes(in []byte) []byte {
var (
pkixPath = field.NewPath("spec", "pkixPublicKey")
popPath = field.NewPath("spec", "proofOfPossession")
pkcs10ReqPath = field.NewPath("spec", "stubPKCS10Request")
certChainPath = field.NewPath("status", "certificateChain")
notBeforePath = field.NewPath("status", "notBefore")
notAfterPath = field.NewPath("status", "notAfter")
@ -826,12 +926,24 @@ func ValidatePodCertificateRequestStatusUpdate(newReq, oldReq *certificates.PodC
}
}
// Was the certificate issued to the public key in the spec?
wantPKAny, err := x509.ParsePKIXPublicKey(oldReq.Spec.PKIXPublicKey)
if err != nil {
allErrors = append(allErrors, field.Invalid(pkixPath, oldReq.Spec.PKIXPublicKey, "must be a valid PKIX-serialized public key"))
return allErrors
// Get the public key from either StubPKCS10Request or PKIXPublicKey.
var wantPKAny crypto.PublicKey
if len(oldReq.Spec.StubPKCS10Request) != 0 {
pkcs10Req, err := x509.ParseCertificateRequest(oldReq.Spec.StubPKCS10Request)
if err != nil {
allErrors = append(allErrors, field.Invalid(pkcs10ReqPath, field.OmitValueType{}, "must be a valid PKCS#10 CSR"))
return allErrors
}
wantPKAny = pkcs10Req.PublicKey
} else {
wantPKAny, err = x509.ParsePKIXPublicKey(oldReq.Spec.PKIXPublicKey)
if err != nil {
allErrors = append(allErrors, field.Invalid(pkixPath, field.OmitValueType{}, "must be a valid PKIX-serialized public key"))
return allErrors
}
}
// Was the certificate issued to the public key in the spec?
switch wantPK := wantPKAny.(type) {
case ed25519.PublicKey:
if !wantPK.Equal(leafCert.PublicKey) {

View file

@ -1610,9 +1610,10 @@ func TestValidateClusterTrustBundleUpdate(t *testing.T) {
func TestValidatePodCertificateRequestCreate(t *testing.T) {
podUID1 := "pod-uid-1"
_, _, ed25519PubPKIX1, ed25519Proof1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1))
_, _, ed25519PubPKIX2, ed25519Proof2 := mustMakeEd25519KeyAndProof(t, []byte("other-value"))
_, _, _, ed25519Proof3 := mustMakeEd25519KeyAndProof(t, []byte(podUID1))
_, _, ed25519PubPKIX1, ed25519Proof1, ed25519CSR1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{})
_, _, ed25519PubPKIX2, ed25519Proof2, _ := mustMakeEd25519KeyAndProof(t, []byte("other-value"), []string{})
_, _, _, ed25519Proof3, _ := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{})
_, _, _, _, ed25519CSR4 := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{"example.com", "foo.example.example"})
_, _, ecdsaP224PubPKIX1, ecdsaP224Proof1 := mustMakeECDSAKeyAndProof(t, elliptic.P224(), []byte(podUID1))
_, _, ecdsaP256PubPKIX1, ecdsaP256Proof1 := mustMakeECDSAKeyAndProof(t, elliptic.P256(), []byte(podUID1))
_, _, ecdsaP384PubPKIX1, ecdsaP384Proof1 := mustMakeECDSAKeyAndProof(t, elliptic.P384(), []byte(podUID1))
@ -1624,7 +1625,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) {
_, _, rsaWrongProofPKIX, rsaWrongProof := mustMakeRSAKeyAndProof(t, 3072, []byte("other-value"))
podUIDEmpty := ""
_, _, pubPKIXEmpty, proofEmpty := mustMakeEd25519KeyAndProof(t, []byte(podUIDEmpty))
_, _, pubPKIXEmpty, proofEmpty, _ := mustMakeEd25519KeyAndProof(t, []byte(podUIDEmpty), []string{})
testCases := []struct {
description string
@ -1653,6 +1654,73 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) {
},
wantErrors: nil,
},
{
description: "valid Ed25519 PCR (using PKCS#10)",
pcr: &capi.PodCertificateRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: capi.PodCertificateRequestSpec{
SignerName: "foo.com/abc",
PodName: "pod-1",
PodUID: types.UID(podUID1),
ServiceAccountName: "sa-1",
ServiceAccountUID: "sa-uid-1",
NodeName: "node-1",
NodeUID: "node-uid-1",
MaxExpirationSeconds: ptr.To[int32](86400),
StubPKCS10Request: ed25519CSR1,
},
},
wantErrors: nil,
},
{
description: "valid Ed25519 PCR (using PKCS#10, with DNS SANs)",
pcr: &capi.PodCertificateRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: capi.PodCertificateRequestSpec{
SignerName: "foo.com/abc",
PodName: "pod-1",
PodUID: types.UID(podUID1),
ServiceAccountName: "sa-1",
ServiceAccountUID: "sa-uid-1",
NodeName: "node-1",
NodeUID: "node-uid-1",
MaxExpirationSeconds: ptr.To[int32](86400),
StubPKCS10Request: ed25519CSR4,
},
},
wantErrors: nil,
},
{
description: "invalid Ed25519 PCR (both StubPKCS10Request and PKIXPublicKey set)",
pcr: &capi.PodCertificateRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: capi.PodCertificateRequestSpec{
SignerName: "foo.com/abc",
PodName: "pod-1",
PodUID: types.UID(podUID1),
ServiceAccountName: "sa-1",
ServiceAccountUID: "sa-uid-1",
NodeName: "node-1",
NodeUID: "node-uid-1",
MaxExpirationSeconds: ptr.To[int32](86400),
PKIXPublicKey: ed25519PubPKIX1,
ProofOfPossession: ed25519Proof1,
StubPKCS10Request: ed25519CSR1,
},
},
wantErrors: field.ErrorList{
field.Invalid(field.NewPath("spec"), field.OmitValueType{}, "exactly one of (stubPKCS10Request) or (pkixPublicKey, proofOfPossession) must be set"),
},
},
{
description: "invalid Ed25519 proof of possession (correct key signed wrong message)",
pcr: &capi.PodCertificateRequest{
@ -2187,7 +2255,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) {
NodeUID: "node-uid-1",
MaxExpirationSeconds: ptr.To[int32](86400),
PKIXPublicKey: make([]byte, capi.MaxPKIXPublicKeySize+1),
ProofOfPossession: []byte{},
ProofOfPossession: []byte("abc"),
},
},
wantErrors: field.ErrorList{
@ -2210,7 +2278,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) {
NodeName: "node-1",
NodeUID: "node-uid-1",
MaxExpirationSeconds: ptr.To[int32](86400),
PKIXPublicKey: []byte{},
PKIXPublicKey: ed25519PubPKIX1,
ProofOfPossession: make([]byte, capi.MaxProofOfPossessionSize+1),
},
},
@ -2308,7 +2376,7 @@ func TestValidatePodCertificateRequestCreate(t *testing.T) {
func TestValidatePodCertificateRequestUpdate(t *testing.T) {
podUID1 := "pod-uid-1"
_, _, pubPKIX1, proof1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1))
_, _, pubPKIX1, proof1, _ := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{})
testCases := []struct {
description string
@ -2396,7 +2464,7 @@ func TestValidatePodCertificateRequestStatusUpdate(t *testing.T) {
intermediateCACertDER, intermediateCAPrivKey := mustMakeIntermediateCA(t, caCertDER, caPrivKey)
podUID1 := "pod-uid-1"
_, pub1, pubPKIX1, proof1 := mustMakeEd25519KeyAndProof(t, []byte(podUID1))
_, pub1, pubPKIX1, proof1, _ := mustMakeEd25519KeyAndProof(t, []byte(podUID1), []string{})
pod1Cert1 := mustSignCertForPublicKey(t, 24*time.Hour, pub1, caCertDER, caPrivKey, false, "", "")
pod1Cert2 := mustSignCertForPublicKey(t, 18*time.Hour, pub1, caCertDER, caPrivKey, false, "", "")
@ -4438,7 +4506,7 @@ func mustParseTime(t *testing.T, stamp string) time.Time {
return got
}
func mustMakeEd25519KeyAndProof(t *testing.T, toBeSigned []byte) (ed25519.PrivateKey, ed25519.PublicKey, []byte, []byte) {
func mustMakeEd25519KeyAndProof(t *testing.T, toBeSigned []byte, pkcs10DNSSANS []string) (ed25519.PrivateKey, ed25519.PublicKey, []byte, []byte, []byte) {
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("Error while generating ed25519 key: %v", err)
@ -4448,7 +4516,13 @@ func mustMakeEd25519KeyAndProof(t *testing.T, toBeSigned []byte) (ed25519.Privat
t.Fatalf("Error while marshaling PKIX public key: %v", err)
}
sig := ed25519.Sign(priv, toBeSigned)
return priv, pub, pubPKIX, sig
pkcs10DER, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{DNSNames: pkcs10DNSSANS}, priv)
if err != nil {
t.Fatalf("Error while creating PKCS#10 certificate signing request: %v", err)
}
return priv, pub, pubPKIX, sig, pkcs10DER
}
func mustMakeECDSAKeyAndProof(t *testing.T, curve elliptic.Curve, toBeSigned []byte) (*ecdsa.PrivateKey, *ecdsa.PublicKey, []byte, []byte) {

View file

@ -25,7 +25,6 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
@ -742,18 +741,14 @@ func (m *IssuingManager) createPodCertificateRequest(
podName string, podUID types.UID,
serviceAccountName string, serviceAccountUID types.UID,
nodeName types.NodeName, nodeUID types.UID,
signerName, keyType string, maxExpirationSeconds *int32, userAnnotations map[string]string) ([]byte, *certificatesv1beta1.PodCertificateRequest, error) {
privateKey, publicKey, proof, err := generateKeyAndProof(keyType, []byte(podUID))
signerName, keyType string, maxExpirationSeconds *int32,
userAnnotations map[string]string,
) ([]byte, *certificatesv1beta1.PodCertificateRequest, error) {
privateKey, pkcs10Req, err := generateKeyAndProof(keyType)
if err != nil {
return nil, nil, fmt.Errorf("while generating keypair: %w", err)
}
pkixPublicKey, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, nil, fmt.Errorf("while marshaling public key: %w", err)
}
keyPEM, err := pemEncodeKey(privateKey)
if err != nil {
return nil, nil, fmt.Errorf("while PEM-encoding private key: %w", err)
@ -781,8 +776,7 @@ func (m *IssuingManager) createPodCertificateRequest(
NodeName: nodeName,
NodeUID: nodeUID,
MaxExpirationSeconds: maxExpirationSeconds,
PKIXPublicKey: pkixPublicKey,
ProofOfPossession: proof,
StubPKCS10Request: pkcs10Req,
UnverifiedUserAnnotations: userAnnotations,
},
}
@ -877,73 +871,57 @@ func (m *IssuingManager) MetricReport() *MetricReport {
return report
}
func hashBytes(in []byte) []byte {
out := sha256.Sum256(in)
return out[:]
}
func generateKeyAndProof(keyType string) (crypto.PrivateKey, []byte, error) {
var privKey crypto.PrivateKey
func generateKeyAndProof(keyType string, toBeSigned []byte) (privKey crypto.PrivateKey, pubKey crypto.PublicKey, sig []byte, err error) {
switch keyType {
case "RSA3072":
key, err := rsa.GenerateKey(rand.Reader, 3072)
priv, err := rsa.GenerateKey(rand.Reader, 3072)
if err != nil {
return nil, nil, nil, fmt.Errorf("while generating RSA 3072 key: %w", err)
return nil, nil, fmt.Errorf("while generating RSA 3072 key: %w", err)
}
sig, err := rsa.SignPSS(rand.Reader, key, crypto.SHA256, hashBytes(toBeSigned), nil)
if err != nil {
return nil, nil, nil, fmt.Errorf("while signing proof: %w", err)
}
return key, &key.PublicKey, sig, nil
privKey = priv
case "RSA4096":
key, err := rsa.GenerateKey(rand.Reader, 4096)
priv, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, nil, fmt.Errorf("while generating RSA 4096 key: %w", err)
return nil, nil, fmt.Errorf("while generating RSA 4096 key: %w", err)
}
sig, err := rsa.SignPSS(rand.Reader, key, crypto.SHA256, hashBytes(toBeSigned), nil)
if err != nil {
return nil, nil, nil, fmt.Errorf("while signing proof: %w", err)
}
return key, &key.PublicKey, sig, nil
privKey = priv
case "ECDSAP256":
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, nil, fmt.Errorf("while generating ECDSA P256 key: %w", err)
return nil, nil, fmt.Errorf("while generating ECDSA P256 key: %w", err)
}
sig, err := ecdsa.SignASN1(rand.Reader, key, hashBytes(toBeSigned))
if err != nil {
return nil, nil, nil, fmt.Errorf("while signing proof: %w", err)
}
return key, &key.PublicKey, sig, nil
privKey = priv
case "ECDSAP384":
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, nil, nil, fmt.Errorf("while generating ECDSA P384 key: %w", err)
return nil, nil, fmt.Errorf("while generating ECDSA P384 key: %w", err)
}
sig, err := ecdsa.SignASN1(rand.Reader, key, hashBytes(toBeSigned))
if err != nil {
return nil, nil, nil, fmt.Errorf("while signing proof: %w", err)
}
return key, &key.PublicKey, sig, nil
privKey = priv
case "ECDSAP521":
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, nil, nil, fmt.Errorf("while generating ECDSA P521 key: %w", err)
return nil, nil, fmt.Errorf("while generating ECDSA P521 key: %w", err)
}
sig, err := ecdsa.SignASN1(rand.Reader, key, hashBytes(toBeSigned))
if err != nil {
return nil, nil, nil, fmt.Errorf("while signing proof: %w", err)
}
return key, &key.PublicKey, sig, nil
privKey = priv
case "ED25519":
pub, priv, err := ed25519.GenerateKey(rand.Reader)
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, nil, fmt.Errorf("while generating Ed25519 key: %w", err)
return nil, nil, fmt.Errorf("while generating Ed25519 key: %w", err)
}
sig := ed25519.Sign(priv, toBeSigned)
return priv, pub, sig, nil
privKey = priv
default:
return nil, nil, nil, fmt.Errorf("unknown key type %q", keyType)
return nil, nil, fmt.Errorf("unknown key type %q", keyType)
}
tmpl := &x509.CertificateRequest{}
pkcs10Req, err := x509.CreateCertificateRequest(rand.Reader, tmpl, privKey)
if err != nil {
return nil, nil, fmt.Errorf("while generating stub PKCS#10 request: %w", err)
}
return privKey, pkcs10Req, nil
}
func pemEncodeKey(key crypto.PrivateKey) ([]byte, error) {

View file

@ -119,7 +119,7 @@ func NewStatusStrategy(strategy *Strategy, authorizer authorizer.Authorizer, clo
// and should not be modified by the user.
func (s *StatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"certificates.k8s.io/v1alpha1": fieldpath.NewSet(
"certificates.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}

View file

@ -438,8 +438,7 @@ type PodCertificateRequestSpec struct {
// +default=86400
MaxExpirationSeconds *int32 `json:"maxExpirationSeconds,omitempty" protobuf:"varint,8,opt,name=maxExpirationSeconds"`
// pkixPublicKey is the PKIX-serialized public key the signer will issue the
// certificate to.
// The PKIX-serialized public key the signer will issue the certificate to.
//
// The key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521,
// or ED25519. Note that this list may be expanded in the future.
@ -451,11 +450,16 @@ type PodCertificateRequestSpec struct {
// "UnsupportedKeyType". It may also suggest a key type that it does support
// in the message field.
//
// +required
// DEPRECATED: This field is replaced by StubPKCS10Request. If
// StubPKCS10Request is set, this field must be empty. Signer
// implementations should extract the public key from the StubPKCS10Request
// field.
//
// +optional
PKIXPublicKey []byte `json:"pkixPublicKey" protobuf:"bytes,9,opt,name=pkixPublicKey"`
// proofOfPossession proves that the requesting kubelet holds the private
// key corresponding to pkixPublicKey.
// A proof that the requesting kubelet holds the private key corresponding
// to pkixPublicKey.
//
// It is contructed by signing the ASCII bytes of the pod's UID using
// `pkixPublicKey`.
@ -472,12 +476,42 @@ type PodCertificateRequestSpec struct {
// golang library function crypto/ecdsa.SignASN1)
//
// If the key is an ED25519 key, the the signature is as described by the
// [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by
// the golang library crypto/ed25519.Sign).
// [ED25519 Specification](https://ed25519.cr.yp.to/) (as implemented by the
// golang library crypto/ed25519.Sign).
//
// +required
// DEPRECATED: This field is replaced by StubPKCS10Request. If
// StubPKCS10Request is set, this field must be empty.
//
// +optional
ProofOfPossession []byte `json:"proofOfPossession" protobuf:"bytes,10,opt,name=proofOfPossession"`
// A PKCS#10 certificate signing request (DER-serialized) generated by
// Kubelet using the subject private key.
//
// Most signer implementations will ignore the contents of the CSR except to
// extract the subject public key. The API server automatically verifies the
// CSR signature during admission, so the signer does not need to repeat the
// verification.
//
// The subject public key must be one of RSA3072, RSA4096, ECDSAP256,
// ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in
// the future.
//
// Signer implementations do not need to support all key types supported by
// kube-apiserver and kubelet. If a signer does not support the key type
// used for a given PodCertificateRequest, it must deny the request by
// setting a status.conditions entry with a type of "Denied" and a reason of
// "UnsupportedKeyType". It may also suggest a key type that it does support
// in the message field.
//
// Some CA implementations require that the client (the signer
// implementation, in this case) provide a PKCS#10 certificate signing
// request, even if the CA only extracts the subject public key from the
// request. To enable compatibility with these CAs, Kubelet will generate a
// stub PKCS#10 request that the signer implementation can then pass on to
// the CA.
StubPKCS10Request []byte `json:"stubPKCS10Request" protobuf:"bytes,12,opt,name=stubPKCS10Request"`
// unverifiedUserAnnotations allow pod authors to pass additional information to
// the signer implementation. Kubernetes does not restrict or validate this
// metadata in any way.

View file

@ -229,9 +229,9 @@ func (c *Controller) handlePCR(ctx context.Context, pcr *certsv1beta1.PodCertifi
return nil
}
subjectPublicKey, err := x509.ParsePKIXPublicKey(pcr.Spec.PKIXPublicKey)
req, err := x509.ParseCertificateRequest(pcr.Spec.StubPKCS10Request)
if err != nil {
return fmt.Errorf("while parsing subject public key: %w", err)
return fmt.Errorf("while parsing PKCS#10 request: %w", err)
}
// If our signer had an opinion on which key types were allowable, it would
@ -276,7 +276,7 @@ func (c *Controller) handlePCR(ctx context.Context, pcr *certsv1beta1.PodCertifi
return fmt.Errorf("while parsing signing certificate: %w", err)
}
subjectCertDER, err := x509.CreateCertificate(rand.Reader, template, signingCert, subjectPublicKey, c.caKeys[len(c.caKeys)-1])
subjectCertDER, err := x509.CreateCertificate(rand.Reader, template, signingCert, req.PublicKey, c.caKeys[len(c.caKeys)-1])
if err != nil {
return fmt.Errorf("while signing subject cert: %w", err)
}