diff --git a/deploy/crds/crd-clusterissuers.yaml b/deploy/crds/crd-clusterissuers.yaml index e243de3a5..c20c9752b 100644 --- a/deploy/crds/crd-clusterissuers.yaml +++ b/deploy/crds/crd-clusterissuers.yaml @@ -1081,6 +1081,11 @@ spec: type: array items: type: string + issuingCertificateURLs: + description: IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. As an example, such a URL might be "http://ca.domain.com/ca.crt". + type: array + items: + type: string ocspServers: description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". type: array diff --git a/deploy/crds/crd-issuers.yaml b/deploy/crds/crd-issuers.yaml index f0c9230b2..7ad039600 100644 --- a/deploy/crds/crd-issuers.yaml +++ b/deploy/crds/crd-issuers.yaml @@ -1081,6 +1081,11 @@ spec: type: array items: type: string + issuingCertificateURLs: + description: IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. As an example, such a URL might be "http://ca.domain.com/ca.crt". + type: array + items: + type: string ocspServers: description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". type: array diff --git a/internal/apis/certmanager/types_issuer.go b/internal/apis/certmanager/types_issuer.go index 38f7438fb..aac8b24c8 100644 --- a/internal/apis/certmanager/types_issuer.go +++ b/internal/apis/certmanager/types_issuer.go @@ -292,6 +292,12 @@ type CAIssuer struct { // certificate will be issued with no OCSP servers set. For example, an // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". OCSPServers []string + + // IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + // it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + // As an example, such a URL might be "http://ca.domain.com/ca.crt". + // +optional + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` } // IssuerStatus contains status information about an Issuer diff --git a/internal/apis/certmanager/v1/zz_generated.conversion.go b/internal/apis/certmanager/v1/zz_generated.conversion.go index 41e6cd26b..c1dc16a7f 100644 --- a/internal/apis/certmanager/v1/zz_generated.conversion.go +++ b/internal/apis/certmanager/v1/zz_generated.conversion.go @@ -401,6 +401,7 @@ func autoConvert_v1_CAIssuer_To_certmanager_CAIssuer(in *v1.CAIssuer, out *certm out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } @@ -413,6 +414,7 @@ func autoConvert_certmanager_CAIssuer_To_v1_CAIssuer(in *certmanager.CAIssuer, o out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } diff --git a/internal/apis/certmanager/v1alpha2/types_issuer.go b/internal/apis/certmanager/v1alpha2/types_issuer.go index 32fbeb2e6..7cff96e37 100644 --- a/internal/apis/certmanager/v1alpha2/types_issuer.go +++ b/internal/apis/certmanager/v1alpha2/types_issuer.go @@ -309,6 +309,12 @@ type CAIssuer struct { // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". // +optional OCSPServers []string `json:"ocspServers,omitempty"` + + // IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + // it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + // As an example, such a URL might be "http://ca.domain.com/ca.crt". + // +optional + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` } // IssuerStatus contains status information about an Issuer diff --git a/internal/apis/certmanager/v1alpha2/zz_generated.conversion.go b/internal/apis/certmanager/v1alpha2/zz_generated.conversion.go index 427b8c168..6d96ceae1 100644 --- a/internal/apis/certmanager/v1alpha2/zz_generated.conversion.go +++ b/internal/apis/certmanager/v1alpha2/zz_generated.conversion.go @@ -399,6 +399,7 @@ func autoConvert_v1alpha2_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *ce out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } @@ -411,6 +412,7 @@ func autoConvert_certmanager_CAIssuer_To_v1alpha2_CAIssuer(in *certmanager.CAIss out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } diff --git a/internal/apis/certmanager/v1alpha2/zz_generated.deepcopy.go b/internal/apis/certmanager/v1alpha2/zz_generated.deepcopy.go index fba61454a..a49652f3c 100644 --- a/internal/apis/certmanager/v1alpha2/zz_generated.deepcopy.go +++ b/internal/apis/certmanager/v1alpha2/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IssuingCertificateURLs != nil { + in, out := &in.IssuingCertificateURLs, &out.IssuingCertificateURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/internal/apis/certmanager/v1alpha3/types_issuer.go b/internal/apis/certmanager/v1alpha3/types_issuer.go index a512cb933..32c073bd3 100644 --- a/internal/apis/certmanager/v1alpha3/types_issuer.go +++ b/internal/apis/certmanager/v1alpha3/types_issuer.go @@ -309,6 +309,12 @@ type CAIssuer struct { // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". // +optional OCSPServers []string `json:"ocspServers,omitempty"` + + // IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + // it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + // As an example, such a URL might be "http://ca.domain.com/ca.crt". + // +optional + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` } // IssuerStatus contains status information about an Issuer diff --git a/internal/apis/certmanager/v1alpha3/zz_generated.conversion.go b/internal/apis/certmanager/v1alpha3/zz_generated.conversion.go index 958d721f4..0a571bbfa 100644 --- a/internal/apis/certmanager/v1alpha3/zz_generated.conversion.go +++ b/internal/apis/certmanager/v1alpha3/zz_generated.conversion.go @@ -399,6 +399,7 @@ func autoConvert_v1alpha3_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *ce out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } @@ -411,6 +412,7 @@ func autoConvert_certmanager_CAIssuer_To_v1alpha3_CAIssuer(in *certmanager.CAIss out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } diff --git a/internal/apis/certmanager/v1alpha3/zz_generated.deepcopy.go b/internal/apis/certmanager/v1alpha3/zz_generated.deepcopy.go index 6f3bcaebc..411b55853 100644 --- a/internal/apis/certmanager/v1alpha3/zz_generated.deepcopy.go +++ b/internal/apis/certmanager/v1alpha3/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IssuingCertificateURLs != nil { + in, out := &in.IssuingCertificateURLs, &out.IssuingCertificateURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/internal/apis/certmanager/v1beta1/types_issuer.go b/internal/apis/certmanager/v1beta1/types_issuer.go index 6bbf12583..63d71ab4a 100644 --- a/internal/apis/certmanager/v1beta1/types_issuer.go +++ b/internal/apis/certmanager/v1beta1/types_issuer.go @@ -311,6 +311,12 @@ type CAIssuer struct { // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". // +optional OCSPServers []string `json:"ocspServers,omitempty"` + + // IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + // it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + // As an example, such a URL might be "http://ca.domain.com/ca.crt". + // +optional + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` } // IssuerStatus contains status information about an Issuer diff --git a/internal/apis/certmanager/v1beta1/zz_generated.conversion.go b/internal/apis/certmanager/v1beta1/zz_generated.conversion.go index 72b72178e..c7505b57d 100644 --- a/internal/apis/certmanager/v1beta1/zz_generated.conversion.go +++ b/internal/apis/certmanager/v1beta1/zz_generated.conversion.go @@ -399,6 +399,7 @@ func autoConvert_v1beta1_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *cer out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } @@ -411,6 +412,7 @@ func autoConvert_certmanager_CAIssuer_To_v1beta1_CAIssuer(in *certmanager.CAIssu out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } diff --git a/internal/apis/certmanager/v1beta1/zz_generated.deepcopy.go b/internal/apis/certmanager/v1beta1/zz_generated.deepcopy.go index 7644138e1..d53aaf364 100644 --- a/internal/apis/certmanager/v1beta1/zz_generated.deepcopy.go +++ b/internal/apis/certmanager/v1beta1/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IssuingCertificateURLs != nil { + in, out := &in.IssuingCertificateURLs, &out.IssuingCertificateURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/internal/apis/certmanager/validation/issuer.go b/internal/apis/certmanager/validation/issuer.go index 5f34983a2..be8e3f2d4 100644 --- a/internal/apis/certmanager/validation/issuer.go +++ b/internal/apis/certmanager/validation/issuer.go @@ -244,6 +244,11 @@ func ValidateCAIssuerConfig(iss *certmanager.CAIssuer, fldPath *field.Path) fiel el = append(el, field.Invalid(fldPath.Child("ocspServer").Index(i), ocspURL, "must be a valid URL, e.g., http://ocsp.int-x3.letsencrypt.org")) } } + for i, issuerURL := range iss.IssuingCertificateURLs { + if issuerURL == "" { + el = append(el, field.Invalid(fldPath.Child("issuingCertificateURLs").Index(i), issuerURL, "must be a valid URL")) + } + } return el } diff --git a/internal/apis/certmanager/validation/issuer_test.go b/internal/apis/certmanager/validation/issuer_test.go index 0a433cbe1..feecb2b7c 100644 --- a/internal/apis/certmanager/validation/issuer_test.go +++ b/internal/apis/certmanager/validation/issuer_test.go @@ -737,6 +737,30 @@ func TestValidateIssuerSpec(t *testing.T) { field.Invalid(fldPath.Child("ca", "ocspServer").Index(0), "", `must be a valid URL, e.g., http://ocsp.int-x3.letsencrypt.org`), }, }, + "valid IssuingCertificateURLs": { + spec: &cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + CA: &cmapi.CAIssuer{ + SecretName: "valid", + IssuingCertificateURLs: []string{"http://ca.example.com/ca.crt"}, + }, + }, + }, + errs: []*field.Error{}, + }, + "invalid IssuingCertificateURLs": { + spec: &cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + CA: &cmapi.CAIssuer{ + SecretName: "valid", + IssuingCertificateURLs: []string{""}, + }, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("ca", "issuingCertificateURLs").Index(0), "", `must be a valid URL`), + }, + }, } for n, s := range scenarios { t.Run(n, func(t *testing.T) { diff --git a/internal/apis/certmanager/zz_generated.deepcopy.go b/internal/apis/certmanager/zz_generated.deepcopy.go index 67361a89e..1ef77825f 100644 --- a/internal/apis/certmanager/zz_generated.deepcopy.go +++ b/internal/apis/certmanager/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IssuingCertificateURLs != nil { + in, out := &in.IssuingCertificateURLs, &out.IssuingCertificateURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/apis/certmanager/v1/types_issuer.go b/pkg/apis/certmanager/v1/types_issuer.go index c901d9e7a..4d5fc4475 100644 --- a/pkg/apis/certmanager/v1/types_issuer.go +++ b/pkg/apis/certmanager/v1/types_issuer.go @@ -314,6 +314,12 @@ type CAIssuer struct { // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". // +optional OCSPServers []string `json:"ocspServers,omitempty"` + + // IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + // it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + // As an example, such a URL might be "http://ca.domain.com/ca.crt". + // +optional + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` } // IssuerStatus contains status information about an Issuer diff --git a/pkg/apis/certmanager/v1/zz_generated.deepcopy.go b/pkg/apis/certmanager/v1/zz_generated.deepcopy.go index 8ba5ea3aa..56ab14501 100644 --- a/pkg/apis/certmanager/v1/zz_generated.deepcopy.go +++ b/pkg/apis/certmanager/v1/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IssuingCertificateURLs != nil { + in, out := &in.IssuingCertificateURLs, &out.IssuingCertificateURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/controller/certificaterequests/ca/ca.go b/pkg/controller/certificaterequests/ca/ca.go index 9b7d90826..f4c7876c7 100644 --- a/pkg/controller/certificaterequests/ca/ca.go +++ b/pkg/controller/certificaterequests/ca/ca.go @@ -128,6 +128,7 @@ func (c *CA) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerObj c template.CRLDistributionPoints = issuerObj.GetSpec().CA.CRLDistributionPoints template.OCSPServer = issuerObj.GetSpec().CA.OCSPServers + template.IssuingCertificateURL = issuerObj.GetSpec().CA.IssuingCertificateURLs bundle, err := c.signingFn(caCerts, caKey, template) if err != nil { diff --git a/pkg/controller/certificaterequests/ca/ca_test.go b/pkg/controller/certificaterequests/ca/ca_test.go index 43a42bda2..339918eaa 100644 --- a/pkg/controller/certificaterequests/ca/ca_test.go +++ b/pkg/controller/certificaterequests/ca/ca_test.go @@ -552,6 +552,24 @@ func TestCA_Sign(t *testing.T) { assert.Equal(t, []string{"http://ocsp-v3.example.org"}, got.OCSPServer) }, }, + "when the Issuer has IssuingCertificateURL set, it should appear on the signed ca": { + givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), + givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{ + SecretName: "secret-1", + IssuingCertificateURLs: []string{"http://ca.letsencrypt.org/ca.crt"}, + })), + givenCR: gen.CertificateRequest("cr-1", + gen.SetCertificateRequestCSR(testCSR), + gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + Name: "issuer-1", + Group: certmanager.GroupName, + Kind: "Issuer", + }), + ), + assertSignedCert: func(t *testing.T, got *x509.Certificate) { + assert.Equal(t, []string{"http://ca.letsencrypt.org/ca.crt"}, got.IssuingCertificateURL) + }, + }, "when the Issuer has crlDistributionPoints set, it should appear on the signed ca ": { givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{ diff --git a/pkg/controller/certificatesigningrequests/ca/ca.go b/pkg/controller/certificatesigningrequests/ca/ca.go index e0139cb4e..a7aebf0a7 100644 --- a/pkg/controller/certificatesigningrequests/ca/ca.go +++ b/pkg/controller/certificatesigningrequests/ca/ca.go @@ -129,6 +129,7 @@ func (c *CA) Sign(ctx context.Context, csr *certificatesv1.CertificateSigningReq template.CRLDistributionPoints = issuerObj.GetSpec().CA.CRLDistributionPoints template.OCSPServer = issuerObj.GetSpec().CA.OCSPServers + template.IssuingCertificateURL = issuerObj.GetSpec().CA.IssuingCertificateURLs bundle, err := c.signingFn(caCerts, caKey, template) if err != nil { diff --git a/pkg/controller/certificatesigningrequests/ca/ca_test.go b/pkg/controller/certificatesigningrequests/ca/ca_test.go index 070bcca22..da190bef0 100644 --- a/pkg/controller/certificatesigningrequests/ca/ca_test.go +++ b/pkg/controller/certificatesigningrequests/ca/ca_test.go @@ -705,6 +705,20 @@ func TestCA_Sign(t *testing.T) { assert.Equal(t, []string{"http://ocsp-v3.example.org"}, got.OCSPServer) }, }, + "when the Issuer has issuingCertificateURLs set, it should appear on the signed ca": { + givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), + givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{ + SecretName: "secret-1", + IssuingCertificateURLs: []string{"http://ca.example.com/ca.crt"}, + })), + givenCSR: gen.CertificateSigningRequest("cr-1", + gen.SetCertificateSigningRequestRequest(testCSR), + gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/"+gen.DefaultTestNamespace+".issuer-1"), + ), + assertSignedCert: func(t *testing.T, got *x509.Certificate) { + assert.Equal(t, []string{"http://ca.example.com/ca.crt"}, got.IssuingCertificateURL) + }, + }, "when the Issuer has crlDistributionPoints set, it should appear on the signed ca ": { givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{