This commit is contained in:
Taahir Ahmed 2026-02-03 17:00:39 -08:00 committed by GitHub
commit b9232e9800
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 510 additions and 115 deletions

View file

@ -5297,7 +5297,7 @@
"type": "string"
},
"pkixPublicKey": {
"description": "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.",
"description": "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nDEPRECATED: 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.",
"format": "byte",
"type": "string"
},
@ -5310,7 +5310,7 @@
"type": "string"
},
"proofOfPossession": {
"description": "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).",
"description": "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).\n\nDEPRECATED: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.",
"format": "byte",
"type": "string"
},
@ -5326,6 +5326,11 @@
"description": "signerName indicates the requested signer.\n\nAll signer names beginning with `kubernetes.io` are reserved for use by the Kubernetes project. There is currently one well-known signer documented by the Kubernetes project, `kubernetes.io/kube-apiserver-client-pod`, which will issue client certificates understood by kube-apiserver. It is currently unimplemented.",
"type": "string"
},
"stubPKCS10Request": {
"description": "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost 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.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nSome 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.",
"format": "byte",
"type": "string"
},
"unverifiedUserAnnotations": {
"additionalProperties": {
"type": "string"
@ -5342,8 +5347,7 @@
"serviceAccountUID",
"nodeName",
"nodeUID",
"pkixPublicKey",
"proofOfPossession"
"stubPKCS10Request"
],
"type": "object"
},

View file

@ -222,7 +222,7 @@
"type": "string"
},
"pkixPublicKey": {
"description": "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.",
"description": "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nDEPRECATED: 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.",
"format": "byte",
"type": "string"
},
@ -237,7 +237,7 @@
"type": "string"
},
"proofOfPossession": {
"description": "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).",
"description": "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).\n\nDEPRECATED: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.",
"format": "byte",
"type": "string"
},
@ -256,6 +256,11 @@
"description": "signerName indicates the requested signer.\n\nAll signer names beginning with `kubernetes.io` are reserved for use by the Kubernetes project. There is currently one well-known signer documented by the Kubernetes project, `kubernetes.io/kube-apiserver-client-pod`, which will issue client certificates understood by kube-apiserver. It is currently unimplemented.",
"type": "string"
},
"stubPKCS10Request": {
"description": "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost 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.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nSome 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.",
"format": "byte",
"type": "string"
},
"unverifiedUserAnnotations": {
"additionalProperties": {
"default": "",
@ -273,8 +278,7 @@
"serviceAccountUID",
"nodeName",
"nodeUID",
"pkixPublicKey",
"proofOfPossession"
"stubPKCS10Request"
],
"type": "object"
},

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

@ -463,6 +463,7 @@ func autoConvert_v1beta1_PodCertificateRequestSpec_To_certificates_PodCertificat
out.MaxExpirationSeconds = (*int32)(unsafe.Pointer(in.MaxExpirationSeconds))
out.PKIXPublicKey = *(*[]byte)(unsafe.Pointer(&in.PKIXPublicKey))
out.ProofOfPossession = *(*[]byte)(unsafe.Pointer(&in.ProofOfPossession))
out.StubPKCS10Request = *(*[]byte)(unsafe.Pointer(&in.StubPKCS10Request))
out.UnverifiedUserAnnotations = *(*map[string]string)(unsafe.Pointer(&in.UnverifiedUserAnnotations))
return nil
}
@ -483,6 +484,7 @@ func autoConvert_certificates_PodCertificateRequestSpec_To_v1beta1_PodCertificat
out.MaxExpirationSeconds = (*int32)(unsafe.Pointer(in.MaxExpirationSeconds))
out.PKIXPublicKey = *(*[]byte)(unsafe.Pointer(&in.PKIXPublicKey))
out.ProofOfPossession = *(*[]byte)(unsafe.Pointer(&in.ProofOfPossession))
out.StubPKCS10Request = *(*[]byte)(unsafe.Pointer(&in.StubPKCS10Request))
out.UnverifiedUserAnnotations = *(*map[string]string)(unsafe.Pointer(&in.UnverifiedUserAnnotations))
return nil
}

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

@ -359,6 +359,11 @@ func (in *PodCertificateRequestSpec) DeepCopyInto(out *PodCertificateRequestSpec
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.StubPKCS10Request != nil {
in, out := &in.StubPKCS10Request, &out.StubPKCS10Request
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.UnverifiedUserAnnotations != nil {
in, out := &in.UnverifiedUserAnnotations, &out.UnverifiedUserAnnotations
*out = make(map[string]string, len(*in))

View file

@ -18266,14 +18266,21 @@ func schema_k8sio_api_certificates_v1beta1_PodCertificateRequestSpec(ref common.
},
"pkixPublicKey": {
SchemaProps: spec.SchemaProps{
Description: "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.",
Description: "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nDEPRECATED: 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.",
Type: []string{"string"},
Format: "byte",
},
},
"proofOfPossession": {
SchemaProps: spec.SchemaProps{
Description: "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).",
Description: "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).\n\nDEPRECATED: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.",
Type: []string{"string"},
Format: "byte",
},
},
"stubPKCS10Request": {
SchemaProps: spec.SchemaProps{
Description: "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost 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.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nSome 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.",
Type: []string{"string"},
Format: "byte",
},
@ -18295,7 +18302,7 @@ func schema_k8sio_api_certificates_v1beta1_PodCertificateRequestSpec(ref common.
},
},
},
Required: []string{"signerName", "podName", "podUID", "serviceAccountName", "serviceAccountUID", "nodeName", "nodeUID", "pkixPublicKey", "proofOfPossession"},
Required: []string{"signerName", "podName", "podUID", "serviceAccountName", "serviceAccountUID", "nodeName", "nodeUID", "stubPKCS10Request"},
},
},
}

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

@ -642,6 +642,13 @@ func (m *PodCertificateRequestSpec) MarshalToSizedBuffer(dAtA []byte) (int, erro
_ = i
var l int
_ = l
if m.StubPKCS10Request != nil {
i -= len(m.StubPKCS10Request)
copy(dAtA[i:], m.StubPKCS10Request)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.StubPKCS10Request)))
i--
dAtA[i] = 0x62
}
if len(m.UnverifiedUserAnnotations) > 0 {
keysForUnverifiedUserAnnotations := make([]string, 0, len(m.UnverifiedUserAnnotations))
for k := range m.UnverifiedUserAnnotations {
@ -1058,6 +1065,10 @@ func (m *PodCertificateRequestSpec) Size() (n int) {
n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize))
}
}
if m.StubPKCS10Request != nil {
l = len(m.StubPKCS10Request)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -1274,6 +1285,7 @@ func (this *PodCertificateRequestSpec) String() string {
`PKIXPublicKey:` + valueToStringGenerated(this.PKIXPublicKey) + `,`,
`ProofOfPossession:` + valueToStringGenerated(this.ProofOfPossession) + `,`,
`UnverifiedUserAnnotations:` + mapStringForUnverifiedUserAnnotations + `,`,
`StubPKCS10Request:` + valueToStringGenerated(this.StubPKCS10Request) + `,`,
`}`,
}, "")
return s
@ -3490,6 +3502,40 @@ func (m *PodCertificateRequestSpec) Unmarshal(dAtA []byte) error {
}
m.UnverifiedUserAnnotations[mapkey] = mapvalue
iNdEx = postIndex
case 12:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field StubPKCS10Request", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.StubPKCS10Request = append(m.StubPKCS10Request[:0], dAtA[iNdEx:postIndex]...)
if m.StubPKCS10Request == nil {
m.StubPKCS10Request = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View file

@ -369,8 +369,7 @@ message PodCertificateRequestSpec {
// +default=86400
optional int32 maxExpirationSeconds = 8;
// 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.
@ -382,11 +381,16 @@ message PodCertificateRequestSpec {
// "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
optional bytes pkixPublicKey = 9;
// 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`.
@ -403,12 +407,42 @@ message PodCertificateRequestSpec {
// 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
optional bytes proofOfPossession = 10;
// 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.
optional bytes stubPKCS10Request = 12;
// 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

@ -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

@ -136,8 +136,9 @@ var map_PodCertificateRequestSpec = map[string]string{
"nodeName": "nodeName is the name of the node the pod is assigned to.",
"nodeUID": "nodeUID is the UID of the node the pod is assigned to.",
"maxExpirationSeconds": "maxExpirationSeconds is the maximum lifetime permitted for the certificate.\n\nIf omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver will reject values shorter than 3600 (1 hour). The maximum allowable value is 7862400 (91 days).\n\nThe signer implementation is then free to issue a certificate with any lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 seconds (1 hour). This constraint is enforced by kube-apiserver. `kubernetes.io` signers will never issue certificates with a lifetime longer than 24 hours.",
"pkixPublicKey": "pkixPublicKey is the PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.",
"proofOfPossession": "proofOfPossession proves that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).",
"pkixPublicKey": "The PKIX-serialized public key the signer will issue the certificate to.\n\nThe key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nDEPRECATED: 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.",
"proofOfPossession": "A proof that the requesting kubelet holds the private key corresponding to pkixPublicKey.\n\nIt is contructed by signing the ASCII bytes of the pod's UID using `pkixPublicKey`.\n\nkube-apiserver validates the proof of possession during creation of the PodCertificateRequest.\n\nIf the key is an RSA key, then the signature is over the ASCII bytes of the pod UID, using RSASSA-PSS from RFC 8017 (as implemented by the golang function crypto/rsa.SignPSS with nil options).\n\nIf the key is an ECDSA key, then the signature is as described by [SEC 1, Version 2.0](https://www.secg.org/sec1-v2.pdf) (as implemented by the golang library function crypto/ecdsa.SignASN1)\n\nIf 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).\n\nDEPRECATED: This field is replaced by StubPKCS10Request. If StubPKCS10Request is set, this field must be empty.",
"stubPKCS10Request": "A PKCS#10 certificate signing request (DER-serialized) generated by Kubelet using the subject private key.\n\nMost 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.\n\nThe subject public key must be one of RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, or ED25519. Note that this list may be expanded in the future.\n\nSigner 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.\n\nSome 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.",
"unverifiedUserAnnotations": "unverifiedUserAnnotations allow pod authors to pass additional information to the signer implementation. Kubernetes does not restrict or validate this metadata in any way.\n\nEntries are subject to the same validation as object metadata annotations, with the addition that all keys must be domain-prefixed. No restrictions are placed on values, except an overall size limitation on the entire field.\n\nSigners should document the keys and values they support. Signers should deny requests that contain keys they do not recognize.",
}

View file

@ -364,6 +364,11 @@ func (in *PodCertificateRequestSpec) DeepCopyInto(out *PodCertificateRequestSpec
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.StubPKCS10Request != nil {
in, out := &in.StubPKCS10Request, &out.StubPKCS10Request
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.UnverifiedUserAnnotations != nil {
in, out := &in.UnverifiedUserAnnotations, &out.UnverifiedUserAnnotations
*out = make(map[string]string, len(*in))

View file

@ -54,6 +54,7 @@
"maxExpirationSeconds": 8,
"pkixPublicKey": "CQ==",
"proofOfPossession": "Cg==",
"stubPKCS10Request": "DA==",
"unverifiedUserAnnotations": {
"unverifiedUserAnnotationsKey": "unverifiedUserAnnotationsValue"
}

View file

@ -43,6 +43,7 @@ spec:
serviceAccountName: serviceAccountNameValue
serviceAccountUID: serviceAccountUIDValue
signerName: signerNameValue
stubPKCS10Request: DA==
unverifiedUserAnnotations:
unverifiedUserAnnotationsKey: unverifiedUserAnnotationsValue
status:

View file

@ -62,8 +62,7 @@ type PodCertificateRequestSpecApplyConfiguration struct {
// `kubernetes.io` signers will never issue certificates with a lifetime
// longer than 24 hours.
MaxExpirationSeconds *int32 `json:"maxExpirationSeconds,omitempty"`
// 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.
@ -74,9 +73,14 @@ type PodCertificateRequestSpecApplyConfiguration 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.
PKIXPublicKey []byte `json:"pkixPublicKey,omitempty"`
// 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`.
@ -93,9 +97,38 @@ type PodCertificateRequestSpecApplyConfiguration 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).
//
// DEPRECATED: This field is replaced by StubPKCS10Request. If
// StubPKCS10Request is set, this field must be empty.
ProofOfPossession []byte `json:"proofOfPossession,omitempty"`
// 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,omitempty"`
// unverifiedUserAnnotations allow pod authors to pass additional information to
// the signer implementation. Kubernetes does not restrict or validate this
// metadata in any way.
@ -199,6 +232,16 @@ func (b *PodCertificateRequestSpecApplyConfiguration) WithProofOfPossession(valu
return b
}
// WithStubPKCS10Request adds the given value to the StubPKCS10Request field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the StubPKCS10Request field.
func (b *PodCertificateRequestSpecApplyConfiguration) WithStubPKCS10Request(values ...byte) *PodCertificateRequestSpecApplyConfiguration {
for i := range values {
b.StubPKCS10Request = append(b.StubPKCS10Request, values[i])
}
return b
}
// WithUnverifiedUserAnnotations puts the entries into the UnverifiedUserAnnotations field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the UnverifiedUserAnnotations field,

View file

@ -3983,6 +3983,9 @@ var schemaYAML = typed.YAMLObject(`types:
type:
scalar: string
default: ""
- name: stubPKCS10Request
type:
scalar: string
- name: unverifiedUserAnnotations
type:
map:

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)
}