diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ff7162d0..38bec54dca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,11 +97,15 @@ Please see the [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest - Feature: It is now possible to set `propagation_modes` in the `TracingService` config when using lightstep as the driver. (Thanks to Paul!) ([#4179]) +- Feature: It is now possible to set `crl_secret` in `Host` and `TLSContext` resources to check peer + certificates against a certificate revocation list. ([#1743]) + - Bugfix: When CORS is specified (either in a `Mapping` or in the `Ambassador` `Module`), CORS processing will happen before authentication. This corrects a problem where XHR to authenticated endpoints would fail. [#4179]: https://github.com/emissary-ingress/emissary/pull/4179 +[#1743]: https://github.com/emissary-ingress/emissary/issues/1743 ## [2.2.2] February 25, 2022 [2.2.2]: https://github.com/emissary-ingress/emissary/compare/v2.2.1...v2.2.2 diff --git a/cmd/entrypoint/secrets.go b/cmd/entrypoint/secrets.go index 0b4ce63656..b88e138141 100644 --- a/cmd/entrypoint/secrets.go +++ b/cmd/entrypoint/secrets.go @@ -326,6 +326,10 @@ func findSecretRefs(ctx context.Context, resource kates.Object, secretNamespacin if r.Spec.TLS != nil { // Host.spec.tls.caSecret is the thing to worry about here. secretRef(r.GetNamespace(), r.Spec.TLS.CASecret, secretNamespacing, action) + + if r.Spec.TLS.CRLSecret != "" { + secretRef(r.GetNamespace(), r.Spec.TLS.CRLSecret, secretNamespacing, action) + } } // Host.spec.tlsSecret and Host.spec.acmeProvider.privateKeySecret are native-Kubernetes-style @@ -359,6 +363,13 @@ func findSecretRefs(ctx context.Context, resource kates.Object, secretNamespacin secretRef(r.GetNamespace(), r.Spec.CASecret, secretNamespacing, action) } + if r.Spec.CRLSecret != "" { + if r.Spec.SecretNamespacing != nil { + secretNamespacing = *r.Spec.SecretNamespacing + } + secretRef(r.GetNamespace(), r.Spec.CRLSecret, secretNamespacing, action) + } + case *amb.Module: // This whole thing is a hack. We probably _should_ check to make sure that // this is an Ambassador Module or a TLS Module, but, well, those're the only diff --git a/docker/test-auth/authsvc.crt b/docker/test-auth/authsvc.crt index 8b16152853..fef3e299e6 100644 --- a/docker/test-auth/authsvc.crt +++ b/docker/test-auth/authsvc.crt @@ -12,12 +12,12 @@ GB+YhUDeevxHXoJxavlw3sgIZ+TL/sHcnCMkNZ1bdwmoTwzQnbkwftYm9pO69hyx egPYgNFLaIkLrpZAQxwijZuQU/obFF10CRI6lp5qxJPiSxAR05fiH7FneUlTeSp1 ui1h3RbrQjiLZuDxy+MuTgJSWLXm/PagrTdZTAm9QTyefgDy8vdWXxi/lwu+Kumq ql/Vp5Lxf56x01CizbKvUq6eZ+fePkCdWg+7uTOYMJc9j5CTjMDvAgMBAAGjVTBT -MA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8E +MA4GA1UdDwEB/wQEAwIBojATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8E AjAAMB4GA1UdEQQXMBWCE2F1dGhzdmMuZGF0YXdpcmUuaW8wDQYJKoZIhvcNAQEL -BQADggEBAIjG4MPc+zGTML29gnlbLeEEeKF2pq68pCuVwGhLiU0/dJhkzx+1wg5E -VvVzqik0nshgUwL5v4rn4slR5Sgs4EF94oXdiVNDW53eHBIPLtJabP6KJRkVUiGp -+H0c/BlAfES3x8uVuUgGtqPmPxQwTSLqTDYuV/p+qFmzV7GjQQy81cUcpgyyVrWz -YJ/S/Hm5ZSasxP+RJ5mFKrsqKknMpJ944oP8BRQEU4FuYzQSKHBCIUEFmCVuF9+7 -40UdZbk1zRJOhNISl4DAgR9O/AR+/kEWfFs5co8yt7Dwr9mwdlLWvibEq1uVnzBS -Uv+GJjyLZ6V6rgUSPknETZHlYC+RYpE= +BQADggEBAHlnt5o2nvSPezGzhU1MuqSDBR2y6tRsnj6VxY9OWwjUSXQXxgQuT0HE +3B5m82+JjVXVoePIdbVO98An6nbVZcWUCZ6tjUbyvCoDdAckrxyHxZ2LqoA1ZTFE +InC374n/RXIPVkk67HzN6f0qdwSPRn/SzWCyuMF7AN/tRmu43c+pMO2IF13BMPj4 +sVnASI+lUrQRt0Evuvu1G04HDI3lq2qaFMENxkiY4z5tFCTzWLSP8Jto89dHpK1q +eGJ+HccWHuT3RAV1OZrQt6S0P7mUOz1CiWoQI4ZO/pYdaWLsNa9KuYQCU5D+q1GD +QeOLLqbE0KvdvgD7tfJ45wkFQk3LUEo= -----END CERTIFICATE----- diff --git a/docker/test-shadow/shadowsvc.crt b/docker/test-shadow/shadowsvc.crt index f47ed3492b..88ae9335bf 100644 --- a/docker/test-shadow/shadowsvc.crt +++ b/docker/test-shadow/shadowsvc.crt @@ -12,12 +12,12 @@ SiobAd2ELGo552Ux9piiBq8tS+uz7dOOTagmAusaQayJ2sMMNP8Y5tZQjIevyGIr AsDfEY7Cg/jLH559omS4TAbvvfhwMV2pYIVloBKky2/EvD1Okb4ROg/WdjLrrabx Uys3bNoz6xHq/QJ1+he+39c8kHo/xolp37ia5EQ8NI6wNBqWexRCNKZUOsdQ0w+2 2cb+maRm0N2bZSodAe9kWrvjmddGtYNSt0oh91SovEMEnq87JWgzAgMBAAGjVTBT -MA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8E +MA4GA1UdDwEB/wQEAwIBojATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8E AjAAMB4GA1UdEQQXMBWCE2RlbW9zdmMuZGF0YXdpcmUuaW8wDQYJKoZIhvcNAQEL -BQADggEBAMt3Qc0Fo/p1HYoqULyACilHT/Z9/Z62ud0QmTmYUP+fS3z3Ueo63AEr -4nafFzYEGQ7jOwGEl+873/iXSzDZnO+BqK3B12H9Du4qPMsnxKkpnu5PvB5/GUIH -3sRJYw2FQdYXx+3t6uaBRGnzLjzMv+c64kw0+06Vb9DPgPH6Rjr4ocYteD4aOFGe -7xBsiXjUnYkPnJljyeNVawe2o8TO/mG5/AIAjNfcgh7E1cHeaufWiLeCpwIu6rn3 -dB+VLpTaLw13O1cIw27wIOV8VNAOy7JG9jbDSW9xIWnHiax3ho8+5EOGmLGvTq9r -ExWQ06LjeSmeKyqvgymG4MVYhVygaeU= +BQADggEBAKqV1OKgP4YnVfC0SGerYWEe5dYRdUZ9ggcd+Kw1jInC86wEgjvLoq62 +4wZqf0w5FDkZM7jhFoncsXhOGsmkNUAAIuKCOp6ur5J4pD3v391QgPnnm3mAyTBQ +yfP6wHG3dWtBQfuGq4ocpWCjC/qiOSnnbCh45k4a+5JomilQiDsigKX6Fib9j+gN +2aEaECG281MTTOsENA5lMDlsKNTzDDzMVNcB+8duu3/Rknlt8qmiVF/+93zmRiZM +HId9BPPt6ymBOGEkfPnbedAAse2aMPCkQ1n7U9ZTrlwFW3DRjHeviKWzE3/Y+paD +lcNjs7N19NQYn1S5t98Op5uj3V3cU48= -----END CERTIFICATE----- diff --git a/docs/releaseNotes.yml b/docs/releaseNotes.yml index cd55af026e..15354793bd 100644 --- a/docs/releaseNotes.yml +++ b/docs/releaseNotes.yml @@ -48,6 +48,14 @@ items: github: - title: "#4179" link: https://github.com/emissary-ingress/emissary/pull/4179 + - title: Added support for TLS certificate revocation list + type: feature + body: >- + It is now possible to set `crl_secret` in `Host` and `TLSContext` resources + to check peer certificates against a certificate revocation list. + github: + - title: "#1743" + link: https://github.com/emissary-ingress/emissary/issues/1743 - title: CORS now happens before auth type: bugfix body: >- diff --git a/manifests/emissary/emissary-crds.yaml.in b/manifests/emissary/emissary-crds.yaml.in index 7697f43118..471b062ab1 100644 --- a/manifests/emissary/emissary-crds.yaml.in +++ b/manifests/emissary/emissary-crds.yaml.in @@ -896,6 +896,8 @@ spec: type: integer sni: type: string + v3CRLSecret: + type: string type: object tlsContext: description: "Name of the TLSContext the Host resource is linked with. @@ -1212,6 +1214,8 @@ spec: items: type: string type: array + crl_secret: + type: string ecdh_curves: items: type: string @@ -3529,6 +3533,8 @@ spec: type: boolean sni: type: string + v3CRLSecret: + type: string type: object x-kubernetes-preserve-unknown-fields: true type: object @@ -3577,6 +3583,8 @@ spec: items: type: string type: array + crl_secret: + type: string ecdh_curves: items: type: string diff --git a/pkg/api/getambassador.io/crds.yaml b/pkg/api/getambassador.io/crds.yaml index c6e06981d9..01e885adce 100644 --- a/pkg/api/getambassador.io/crds.yaml +++ b/pkg/api/getambassador.io/crds.yaml @@ -902,6 +902,8 @@ spec: type: integer sni: type: string + v3CRLSecret: + type: string type: object tlsContext: description: "Name of the TLSContext the Host resource is linked with. @@ -1217,6 +1219,8 @@ spec: items: type: string type: array + crl_secret: + type: string ecdh_curves: items: type: string @@ -3592,6 +3596,8 @@ spec: type: boolean sni: type: string + v3CRLSecret: + type: string type: object type: object served: true @@ -3639,6 +3645,8 @@ spec: items: type: string type: array + crl_secret: + type: string ecdh_curves: items: type: string diff --git a/pkg/api/getambassador.io/v2/crd_host.go b/pkg/api/getambassador.io/v2/crd_host.go index 2524692b19..a62637a9a0 100644 --- a/pkg/api/getambassador.io/v2/crd_host.go +++ b/pkg/api/getambassador.io/v2/crd_host.go @@ -152,6 +152,9 @@ type TLSConfig struct { ECDHCurves []string `json:"ecdh_curves,omitempty"` RedirectCleartextFrom *int `json:"redirect_cleartext_from,omitempty"` SNI string `json:"sni,omitempty"` + + // +k8s:conversion-gen:rename=CRLSecret + V3CRLSecret string `json:"v3CRLSecret,omitempty"` } // The first value listed in the Enum marker becomes the "zero" value, diff --git a/pkg/api/getambassador.io/v2/crd_tlscontext.go b/pkg/api/getambassador.io/v2/crd_tlscontext.go index e0504866bd..c73436206e 100644 --- a/pkg/api/getambassador.io/v2/crd_tlscontext.go +++ b/pkg/api/getambassador.io/v2/crd_tlscontext.go @@ -44,6 +44,9 @@ type TLSContextSpec struct { SecretNamespacing *bool `json:"secret_namespacing,omitempty"` RedirectCleartextFrom *int `json:"redirect_cleartext_from,omitempty"` SNI string `json:"sni,omitempty"` + + // +k8s:conversion-gen:rename=CRLSecret + V3CRLSecret string `json:"v3CRLSecret,omitempty"` } // TLSContext is the Schema for the tlscontexts API diff --git a/pkg/api/getambassador.io/v2/zz_generated.conversion.go b/pkg/api/getambassador.io/v2/zz_generated.conversion.go index 0a629a6881..0c705451bc 100644 --- a/pkg/api/getambassador.io/v2/zz_generated.conversion.go +++ b/pkg/api/getambassador.io/v2/zz_generated.conversion.go @@ -5083,7 +5083,58 @@ func autoConvert_v3alpha1_TCPMappingSpec_To_v2_TCPMappingSpec(in *v3alpha1.TCPMa } func autoConvert_v2_TLSConfig_To_v3alpha1_TLSConfig(in *TLSConfig, out *v3alpha1.TLSConfig, s conversion.Scope) error { - *out = v3alpha1.TLSConfig(*in) + if true { + in, out := &in.CertChainFile, &out.CertChainFile + *out = *in + } + if true { + in, out := &in.PrivateKeyFile, &out.PrivateKeyFile + *out = *in + } + if true { + in, out := &in.CASecret, &out.CASecret + *out = *in + } + if true { + in, out := &in.CAcertChainFile, &out.CAcertChainFile + *out = *in + } + if true { + in, out := &in.AlpnProtocols, &out.AlpnProtocols + *out = *in + } + if true { + in, out := &in.CertRequired, &out.CertRequired + *out = *in + } + if true { + in, out := &in.MinTLSVersion, &out.MinTLSVersion + *out = *in + } + if true { + in, out := &in.MaxTLSVersion, &out.MaxTLSVersion + *out = *in + } + if true { + in, out := &in.CipherSuites, &out.CipherSuites + *out = *in + } + if true { + in, out := &in.ECDHCurves, &out.ECDHCurves + *out = *in + } + if true { + in, out := &in.RedirectCleartextFrom, &out.RedirectCleartextFrom + *out = *in + } + if true { + in, out := &in.SNI, &out.SNI + *out = *in + } + if true { + in, out := &in.V3CRLSecret, &out.CRLSecret + *out = *in + } return nil } @@ -5093,7 +5144,58 @@ func Convert_v2_TLSConfig_To_v3alpha1_TLSConfig(in *TLSConfig, out *v3alpha1.TLS } func autoConvert_v3alpha1_TLSConfig_To_v2_TLSConfig(in *v3alpha1.TLSConfig, out *TLSConfig, s conversion.Scope) error { - *out = TLSConfig(*in) + if true { + in, out := &in.CertChainFile, &out.CertChainFile + *out = *in + } + if true { + in, out := &in.PrivateKeyFile, &out.PrivateKeyFile + *out = *in + } + if true { + in, out := &in.CASecret, &out.CASecret + *out = *in + } + if true { + in, out := &in.CAcertChainFile, &out.CAcertChainFile + *out = *in + } + if true { + in, out := &in.CRLSecret, &out.V3CRLSecret + *out = *in + } + if true { + in, out := &in.AlpnProtocols, &out.AlpnProtocols + *out = *in + } + if true { + in, out := &in.CertRequired, &out.CertRequired + *out = *in + } + if true { + in, out := &in.MinTLSVersion, &out.MinTLSVersion + *out = *in + } + if true { + in, out := &in.MaxTLSVersion, &out.MaxTLSVersion + *out = *in + } + if true { + in, out := &in.CipherSuites, &out.CipherSuites + *out = *in + } + if true { + in, out := &in.ECDHCurves, &out.ECDHCurves + *out = *in + } + if true { + in, out := &in.RedirectCleartextFrom, &out.RedirectCleartextFrom + *out = *in + } + if true { + in, out := &in.SNI, &out.SNI + *out = *in + } return nil } @@ -5261,6 +5363,10 @@ func autoConvert_v2_TLSContextSpec_To_v3alpha1_TLSContextSpec(in *TLSContextSpec in, out := &in.SNI, &out.SNI *out = *in } + if true { + in, out := &in.V3CRLSecret, &out.CRLSecret + *out = *in + } return nil } @@ -5300,6 +5406,10 @@ func autoConvert_v3alpha1_TLSContextSpec_To_v2_TLSContextSpec(in *v3alpha1.TLSCo in, out := &in.CACertChainFile, &out.CACertChainFile *out = *in } + if true { + in, out := &in.CRLSecret, &out.V3CRLSecret + *out = *in + } if true { in, out := &in.ALPNProtocols, &out.ALPNProtocols *out = *in diff --git a/pkg/api/getambassador.io/v3alpha1/crd_host.go b/pkg/api/getambassador.io/v3alpha1/crd_host.go index 1467017c8d..030d8a2b85 100644 --- a/pkg/api/getambassador.io/v3alpha1/crd_host.go +++ b/pkg/api/getambassador.io/v3alpha1/crd_host.go @@ -141,6 +141,7 @@ type TLSConfig struct { PrivateKeyFile string `json:"private_key_file,omitempty"` CASecret string `json:"ca_secret,omitempty"` CAcertChainFile string `json:"cacert_chain_file,omitempty"` + CRLSecret string `json:"crl_secret,omitempty"` AlpnProtocols string `json:"alpn_protocols,omitempty"` CertRequired *bool `json:"cert_required,omitempty"` MinTLSVersion string `json:"min_tls_version,omitempty"` diff --git a/pkg/api/getambassador.io/v3alpha1/crd_tlscontext.go b/pkg/api/getambassador.io/v3alpha1/crd_tlscontext.go index 608a6bdae1..3dd758d2f4 100644 --- a/pkg/api/getambassador.io/v3alpha1/crd_tlscontext.go +++ b/pkg/api/getambassador.io/v3alpha1/crd_tlscontext.go @@ -33,6 +33,7 @@ type TLSContextSpec struct { PrivateKeyFile string `json:"private_key_file,omitempty"` CASecret string `json:"ca_secret,omitempty"` CACertChainFile string `json:"cacert_chain_file,omitempty"` + CRLSecret string `json:"crl_secret,omitempty"` ALPNProtocols string `json:"alpn_protocols,omitempty"` CertRequired *bool `json:"cert_required,omitempty"` // +kubebuilder:validation:Enum={"v1.0", "v1.1", "v1.2", "v1.3"} diff --git a/python/ambassador/envoy/v3/v3tls.py b/python/ambassador/envoy/v3/v3tls.py index fa5647f156..e9cdf22021 100644 --- a/python/ambassador/envoy/v3/v3tls.py +++ b/python/ambassador/envoy/v3/v3tls.py @@ -127,6 +127,7 @@ def add_context(self, ctx: IRTLSContext) -> None: ( 'cert_chain_file', self.update_cert_zero, 'certificate_chain' ), ( 'private_key_file', self.update_cert_zero, 'private_key' ), ( 'cacert_chain_file', self.update_validation, 'trusted_ca' ), + ( 'crl_file', self.update_validation, 'crl' ), ]: if secretinfokey in ctx['secret_info']: handler(hkey, ctx['secret_info'][secretinfokey]) diff --git a/python/ambassador/fetch/secret.py b/python/ambassador/fetch/secret.py index 1a8196d823..2cb00b8a12 100644 --- a/python/ambassador/fetch/secret.py +++ b/python/ambassador/fetch/secret.py @@ -27,6 +27,7 @@ class SecretProcessor (ManagedKubernetesProcessor): 'cert-chain.pem', # type="istio.io/key-and-cert" 'key.pem', # type="istio.io/key-and-cert" 'root-cert.pem', # type="istio.io/key-and-cert" + 'crl.pem', # type="Opaque", used for TLS CRL ] def __init__(self, manager: ResourceManager) -> None: diff --git a/python/ambassador/ir/ir.py b/python/ambassador/ir/ir.py index 944ffb191b..644aa8578f 100644 --- a/python/ambassador/ir/ir.py +++ b/python/ambassador/ir/ir.py @@ -729,13 +729,17 @@ def save_secret_info(self, aconf): # # (We include 'user_key' here because ACME private keys use that, and they # should not generate errors.) - if aconf_secret.get('tls_crt') or aconf_secret.get('cert-chain_pem') or aconf_secret.get('user_key'): + # (We include 'crl_pem' here because CRL secrets use that, and they + # should not generate errors.) + if aconf_secret.get('tls_crt') or aconf_secret.get('cert-chain_pem') or aconf_secret.get('user_key') or aconf_secret.get('crl_pem'): secret_info = SecretInfo.from_aconf_secret(aconf_secret) secret_name = secret_info.name secret_namespace = secret_info.namespace self.logger.debug('saving "%s.%s" (from %s) in secret_info', secret_name, secret_namespace, secret_key) self.secret_info[f'{secret_name}.{secret_namespace}'] = secret_info + else: + self.logger.debug('not saving secret_info from %s because there is no public half', secret_key) def save_tls_context(self, ctx: IRTLSContext) -> None: extant_ctx = self.tls_contexts.get(ctx.name, None) @@ -1005,6 +1009,7 @@ def features(self) -> Dict[str, Any]: tls_termination_count = 0 # TLS termination contexts tls_origination_count = 0 # TLS origination contexts + tls_crl_file_count = 0 # CRL files used using_tls_module = False using_tls_contexts = False @@ -1022,6 +1027,9 @@ def features(self) -> Dict[str, Any]: if secret_info.get('cacert_chain_file', None): tls_origination_count += 1 + if secret_info.get('crl_file', None): + tls_crl_file_count += 1 + if ctx.get('_legacy', False): using_tls_module = True @@ -1029,6 +1037,7 @@ def features(self) -> Dict[str, Any]: od['tls_using_contexts'] = using_tls_contexts od['tls_termination_count'] = tls_termination_count od['tls_origination_count'] = tls_origination_count + od['tls_crl_file_count'] = tls_crl_file_count for key in [ 'diagnostics', 'liveness_probe', 'readiness_probe', 'statsd' ]: od[key] = self.ambassador_module.get(key, {}).get('enabled', False) diff --git a/python/ambassador/ir/irhost.py b/python/ambassador/ir/irhost.py index c37fe7dbe4..cd61e37796 100644 --- a/python/ambassador/ir/irhost.py +++ b/python/ambassador/ir/irhost.py @@ -143,6 +143,8 @@ def setup(self, ir: 'IR', aconf: Config) -> bool: 'certChainFile': 'cert_chain_file', 'privateKeyFile': 'private_key_file', 'cacertChainFile': 'cacert_chain_file', + 'crlSecret': 'crl_secret', + 'crlFile': 'crl_file', 'caSecret': 'ca_secret', # 'sni': 'sni' (this field is not required in snake-camel but adding for completeness) } @@ -385,7 +387,7 @@ def matches_httpgroup(self, group: 'IRHTTPMappingGroup') -> bool: # "hostname" or "host", the Mapping code normalizes to "host" internally. # It's possible for group.host_redirect to be None instead of missing, and it's also - # conceivably possible for group.host_redirect.host to be "", which we'd rather be + # conceivably possible for group.host_redirect.host to be "", which we'd rather be # None. Hence we do this two-line dance to massage the various cases. host_redirect = (group.get('host_redirect') or {}).get('host') group_glob = group.get('host') or host_redirect # NOT A TYPO: see above. diff --git a/python/ambassador/ir/irtlscontext.py b/python/ambassador/ir/irtlscontext.py index 5c7ff3b1c8..bf13b92423 100644 --- a/python/ambassador/ir/irtlscontext.py +++ b/python/ambassador/ir/irtlscontext.py @@ -20,7 +20,10 @@ class IRTLSContext(IRResource): 'private_key_file', 'ca_secret', - 'cacert_chain_file' + 'cacert_chain_file', + + 'crl_secret', + 'crl_file', } AllowedKeys: ClassVar = { @@ -218,6 +221,23 @@ def resolve(self) -> bool: self.ir.logger.debug("TLSContext - successfully processed the cert_chain_file, private_key_file, and cacert_chain_file: %s" % self.secret_info) + # OK. Repeat for the crl_secret. + crl_secret = self.secret_info.get('crl_secret') + if crl_secret: + # They gave a secret name for the certificate revocation list. Try loading it. + crls = self.resolve_secret(crl_secret) + + self.ir.logger.debug("resolve_secrets: IR returned secret %s as %s" % (crl_secret, crls)) + + if not crls: + # This is definitively an error: they mentioned a secret, it can't be loaded, + # give up. + self.post_error("TLSContext %s found no certificate revocation list in %s" % (self.name, crls.name)) + secret_valid = False + else: + self.ir.logger.debug("TLSContext %s saved certificate revocation list secret %s" % (self.name, crls.name)) + self.secret_info['crl_file'] = crls.user_path + # OK. Repeat for the ca_secret_name. ca_secret_name = self.secret_info.get('ca_secret') @@ -272,7 +292,7 @@ def resolve(self) -> bool: errors = 0 # self.ir.logger.debug("resolve_secrets before path checks: %s" % self.as_json()) - for key in [ 'cert_chain_file', 'private_key_file', 'cacert_chain_file' ]: + for key in [ 'cert_chain_file', 'private_key_file', 'cacert_chain_file', 'crl_file' ]: path = self.secret_info.get(key, None) if path: @@ -280,7 +300,7 @@ def resolve(self) -> bool: if not fc(path): self.post_error("TLSContext %s found no %s '%s'" % (self.name, key, path)) errors += 1 - elif key != 'cacert_chain_file' and self.get('hosts', None): + elif (not(key == 'cacert_chain_file' or key == 'crl_file')) and self.get('hosts', None): self.post_error("TLSContext %s is missing %s" % (self.name, key)) errors += 1 diff --git a/python/ambassador/utils.py b/python/ambassador/utils.py index 92fff12bc7..aff0c3845c 100644 --- a/python/ambassador/utils.py +++ b/python/ambassador/utils.py @@ -624,13 +624,18 @@ def from_aconf_secret(cls, aconf_object: 'ACResource') -> 'SecretInfo': if not tls_key: tls_key = aconf_object.get('key_pem') + user_key = aconf_object.get('user_key', None) + if not user_key: + # We didn't have a 'user_key', do we have a `crl_pem` instead? + user_key = aconf_object.get('crl_pem', None) + return SecretInfo( aconf_object.name, aconf_object.namespace, aconf_object.secret_type, tls_crt, tls_key, - aconf_object.get('user_key', None), + user_key, aconf_object.get('root-cert_pem', None) ) diff --git a/python/requirements-dev.txt b/python/requirements-dev.txt index 6cd1d35eba..680968b184 100644 --- a/python/requirements-dev.txt +++ b/python/requirements-dev.txt @@ -4,6 +4,7 @@ httpretty mypy packaging pexpect +pyOpenSSL pytest==6.2.5 pytest-cov pytest-rerunfailures @@ -12,6 +13,7 @@ retry # Type stubs types-orjson types-protobuf +types-pyOpenSSL types-pyyaml types-requests types-retry diff --git a/python/tests/integration/manifests/crds.yaml b/python/tests/integration/manifests/crds.yaml index 6855dfff8e..609611188a 100644 --- a/python/tests/integration/manifests/crds.yaml +++ b/python/tests/integration/manifests/crds.yaml @@ -872,6 +872,8 @@ spec: type: integer sni: type: string + v3CRLSecret: + type: string type: object tlsContext: description: "Name of the TLSContext the Host resource is linked with. @@ -1188,6 +1190,8 @@ spec: items: type: string type: array + crl_secret: + type: string ecdh_curves: items: type: string @@ -3446,6 +3450,8 @@ spec: type: boolean sni: type: string + v3CRLSecret: + type: string type: object x-kubernetes-preserve-unknown-fields: true type: object @@ -3494,6 +3500,8 @@ spec: items: type: string type: array + crl_secret: + type: string ecdh_curves: items: type: string diff --git a/python/tests/kat/t_hosts.py b/python/tests/kat/t_hosts.py index 41ec839938..f2bd083cb9 100644 --- a/python/tests/kat/t_hosts.py +++ b/python/tests/kat/t_hosts.py @@ -5,6 +5,7 @@ from abstract_tests import AmbassadorTest, ServiceType, HTTP, Node from tests.selfsigned import TLSCerts +from tests.utils import create_crl_pem_b64 from ambassador import Config @@ -249,6 +250,139 @@ def queries(self): yield Query(self.url("target/cleartext", scheme="http"), expected=301) +class HostCRDManualContextCRL(AmbassadorTest): + """ + A single Host with a manually-specified TLS secret, a manually-specified TLSContext and + a manually specified mTLS config with CRL list too. + """ + target: ServiceType + + def init(self): + if Config.envoy_api_version == "V2": + self.skip_node = True + self.add_default_http_listener = False + self.add_default_https_listener = False + + self.target = HTTP() + + def manifests(self) -> str: + return self.format(''' +--- +apiVersion: getambassador.io/v3alpha1 +kind: Listener +metadata: + name: {self.name.k8s}-listener + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + port: 8443 + protocol: HTTPS + securityModel: XFP + hostBinding: + namespace: + from: SELF +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}-server-manual-crl-secret + labels: + kat-ambassador-id: {self.ambassador_id} +type: kubernetes.io/tls +data: + tls.crt: '''+TLSCerts["ambassador.example.com"].k8s_crt+''' + tls.key: '''+TLSCerts["ambassador.example.com"].k8s_key+''' +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}-ca-manual-crl-secret + labels: + kat-ambassador-id: {self.ambassador_id} +type: kubernetes.io/tls +data: + tls.crt: '''+TLSCerts["master.datawire.io"].k8s_crt+''' + tls.key: "" +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}-crl-manual-crl-secret + labels: + kat-ambassador-id: {self.ambassador_id} +type: Opaque +data: + crl.pem: '''+create_crl_pem_b64(TLSCerts["master.datawire.io"].pubcert, TLSCerts["master.datawire.io"].privkey, [TLSCerts["presto.example.com"].pubcert])+''' +--- +apiVersion: getambassador.io/v3alpha1 +kind: Host +metadata: + name: {self.path.k8s}-manual-crl-host + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + hostname: ambassador.example.com + acmeProvider: + authority: none + mappingSelector: + matchLabels: + hostname: {self.path.k8s}-manual-crl-hostname + tlsSecret: + name: {self.path.k8s}-server-manual-crl-secret +--- +apiVersion: getambassador.io/v3alpha1 +kind: TLSContext +metadata: + name: {self.path.k8s}-manual-crl-host-context + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + hosts: + - ambassador.example.com + ca_secret: {self.path.k8s}-ca-manual-crl-secret + secret: {self.path.k8s}-server-manual-crl-secret + cert_required: true + crl_secret: {self.path.k8s}-crl-manual-crl-secret +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +metadata: + name: {self.path.k8s}-target-mapping + labels: + hostname: {self.path.k8s}-manual-crl-hostname +spec: + ambassador_id: [ {self.ambassador_id} ] + hostname: ambassador.example.com + prefix: / + service: {self.target.path.fqdn} +''') + super().manifests() + + def scheme(self) -> str: + return "https" + + def queries(self): + base = { + 'url': self.url(""), + 'ca_cert': TLSCerts["master.datawire.io"].pubcert, + 'headers': {"Host": "ambassador.example.com"}, + 'sni': True, # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI + } + + yield Query(**base, + error="tls: certificate required") + + yield Query(**base, + client_crt=TLSCerts["presto.example.com"].pubcert, + client_key=TLSCerts["presto.example.com"].privkey, + error="tls: revoked certificate") + + def requirements(self): + yield ("pod", self.path.k8s) + + class HostCRDSeparateTLSContext(AmbassadorTest): target: ServiceType @@ -1098,6 +1232,247 @@ def requirements(self): yield (r[0], query) +class HostCRDClientCertCRLEmptyList(AmbassadorTest): + target: ServiceType + + def init(self): + if Config.envoy_api_version == "V2": + self.skip_node = True + self.target = HTTP() + self.add_default_http_listener = False + self.add_default_https_listener = False + + def manifests(self) -> str: + # Similar to HostCRDClientCertSameNamespace, except we also + # include a Certificate Revocation List in the TLS config + return namespace_manifest("alt3-namespace") + self.format(''' +--- +apiVersion: getambassador.io/v3alpha1 +kind: Listener +metadata: + name: ambassador-listener-8443 # This name is to match existing test stuff + namespace: alt3-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + port: 8443 + protocol: HTTPS + securityModel: XFP + hostBinding: + namespace: + from: SELF +--- +apiVersion: getambassador.io/v3alpha1 +kind: Host +metadata: + name: {self.path.k8s} + namespace: alt3-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + hostname: ambassador.example.com + acmeProvider: + authority: none + tlsSecret: + name: {self.path.k8s}.server + tls: + ca_secret: {self.path.k8s}-ca + cert_required: true + crl_secret: {self.path.k8s}-crl +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}-ca + namespace: alt3-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +type: kubernetes.io/tls +data: + tls.crt: '''+TLSCerts["master.datawire.io"].k8s_crt+''' + tls.key: "" +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}-crl + namespace: alt3-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +type: Opaque +data: + crl.pem: '''+create_crl_pem_b64(TLSCerts["master.datawire.io"].pubcert, TLSCerts["master.datawire.io"].privkey, [])+''' +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}.server + namespace: alt3-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +type: kubernetes.io/tls +data: + tls.crt: '''+TLSCerts["ambassador.example.com"].k8s_crt+''' + tls.key: '''+TLSCerts["ambassador.example.com"].k8s_key+''' +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +metadata: + name: {self.path.k8s} + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + hostname: "*" + prefix: / + service: {self.target.path.fqdn} +''') + super().manifests() + + def scheme(self) -> str: + return "https" + + def queries(self): + base = { + 'url': self.url(""), + 'ca_cert': TLSCerts["master.datawire.io"].pubcert, + 'headers': {"Host": "ambassador.example.com"}, + 'sni': True, # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI + } + + yield Query(**base, + error="tls: certificate required") + + yield Query(**base, + client_crt=TLSCerts["presto.example.com"].pubcert, + client_key=TLSCerts["presto.example.com"].privkey) + + def requirements(self): + yield ("pod", self.path.k8s) + + +class HostCRDClientCertCRLRevokeList(AmbassadorTest): + target: ServiceType + + def init(self): + if Config.envoy_api_version == "V2": + self.skip_node = True + self.target = HTTP() + self.add_default_http_listener = False + self.add_default_https_listener = False + + def manifests(self) -> str: + # Similar to HostCRDClientCertSameNamespace, except we also + # include a Certificate Revocation List in the TLS config + return namespace_manifest("alt4-namespace") + self.format(''' +--- +apiVersion: getambassador.io/v3alpha1 +kind: Listener +metadata: + name: ambassador-listener-8443 # This name is to match existing test stuff + namespace: alt4-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + port: 8443 + protocol: HTTPS + securityModel: XFP + hostBinding: + namespace: + from: SELF +--- +apiVersion: getambassador.io/v3alpha1 +kind: Host +metadata: + name: {self.path.k8s} + namespace: alt4-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + hostname: ambassador.example.com + acmeProvider: + authority: none + tlsSecret: + name: {self.path.k8s}.server + tls: + ca_secret: {self.path.k8s}-ca + cert_required: true + crl_secret: {self.path.k8s}-crl +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}-ca + namespace: alt4-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +type: kubernetes.io/tls +data: + tls.crt: '''+TLSCerts["master.datawire.io"].k8s_crt+''' + tls.key: "" +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}-crl + namespace: alt4-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +type: Opaque +data: + crl.pem: '''+create_crl_pem_b64(TLSCerts["master.datawire.io"].pubcert, TLSCerts["master.datawire.io"].privkey, [TLSCerts["presto.example.com"].pubcert])+''' +--- +apiVersion: v1 +kind: Secret +metadata: + name: {self.path.k8s}.server + namespace: alt4-namespace + labels: + kat-ambassador-id: {self.ambassador_id} +type: kubernetes.io/tls +data: + tls.crt: '''+TLSCerts["ambassador.example.com"].k8s_crt+''' + tls.key: '''+TLSCerts["ambassador.example.com"].k8s_key+''' +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +metadata: + name: {self.path.k8s} + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [ {self.ambassador_id} ] + hostname: "*" + prefix: / + service: {self.target.path.fqdn} +''') + super().manifests() + + def scheme(self) -> str: + return "https" + + def queries(self): + base = { + 'url': self.url(""), + 'ca_cert': TLSCerts["master.datawire.io"].pubcert, + 'headers': {"Host": "ambassador.example.com"}, + 'sni': True, # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI + } + + yield Query(**base, + error="tls: certificate required") + + yield Query(**base, + client_crt=TLSCerts["presto.example.com"].pubcert, + client_key=TLSCerts["presto.example.com"].privkey, + error="tls: revoked certificate") + + def requirements(self): + yield ("pod", self.path.k8s) + + class HostCRDRootRedirectCongratulations(AmbassadorTest): def manifests(self) -> str: diff --git a/python/tests/kat/t_tls.py b/python/tests/kat/t_tls.py index 72b230757e..ea8c196ccb 100644 --- a/python/tests/kat/t_tls.py +++ b/python/tests/kat/t_tls.py @@ -5,6 +5,7 @@ from abstract_tests import AmbassadorTest, HTTP, ServiceType, Node from tests.selfsigned import TLSCerts from tests.integration.manifests import namespace_manifest +from tests.utils import create_crl_pem_b64 bug_404_routes = True # Do we erroneously send 404 responses directly instead of redirect-to-tls first? @@ -273,6 +274,87 @@ def requirements(self): yield (r[0], query) +class ClientCertificateAuthenticationContextCRL(AmbassadorTest): + + def init(self): + self.xfail = "FIXME: IHA" # This test should cover TLSContext with a crl_secret + self.target = HTTP() + + def manifests(self) -> str: + return self.format(f""" +--- +apiVersion: v1 +metadata: + name: ccauthctxcrl-client-secret + labels: + kat-ambassador-id: {self.ambassador_id} +data: + tls.crt: {TLSCerts["master.datawire.io"].k8s_crt} +kind: Secret +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: ccauthctxcrl-server-secret + labels: + kat-ambassador-id: {self.ambassador_id} +type: kubernetes.io/tls +data: + tls.crt: {TLSCerts["ambassador.example.com"].k8s_crt} + tls.key: {TLSCerts["ambassador.example.com"].k8s_key} +--- +apiVersion: v1 +kind: Secret +metadata: + name: ccauthctxcrl-crl-secret + labels: + kat-ambassador-id: {self.ambassador_id} +type: Opaque +data: + crl.pem: {create_crl_pem_b64(TLSCerts["master.datawire.io"].pubcert, TLSCerts["master.datawire.io"].privkey, [TLSCerts["presto.example.com"].pubcert])} +--- +apiVersion: getambassador.io/v3alpha1 +kind: TLSContext +metadata: + name: ccauthctxcrl-tls + labels: + kat-ambassador-id: {self.ambassador_id} +spec: + ambassador_id: [{self.ambassador_id}] + hosts: [ "*" ] + secret: ccauthctxcrl-server-secret + ca_secret: ccauthctxcrl-client-secret + crl_secret: ccauthctxcrl-crl-secret + cert_required: True +""") + super().manifests() + + def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]: + yield self, self.format(""" +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +name: {self.target.path.k8s} +prefix: / +service: {self.target.path.fqdn} +hostname: "*" +""") + + def scheme(self) -> str: + return "https" + + def queries(self): + yield Query(self.url(self.name + "/"), insecure=True, + client_crt=TLSCerts["presto.example.com"].pubcert, + client_key=TLSCerts["presto.example.com"].privkey, + client_cert_required=True, + ca_cert=TLSCerts["master.datawire.io"].pubcert, + error=[ "tls: revoked certificate" ]) + + def requirements(self): + yield ("pod", self.path.k8s) + + class TLSOriginationSecret(AmbassadorTest): def init(self): diff --git a/python/tests/selfsigned.py b/python/tests/selfsigned.py index f4a9d76e58..2c6406b117 100644 --- a/python/tests/selfsigned.py +++ b/python/tests/selfsigned.py @@ -39,15 +39,15 @@ def strip(s: str) -> str: +ljKHLySqvRxwRlCwiZuagQcMfD8nn0vHOMEyj2QymOJ6bx5s0OZcJ72YAVQbYil 4kJQ/obKkrEzMQPORu+q+3dEhuaauJnzOrECpHX/56spSaRYCIkFgoUGfdl+S7Qr lQ4AHCrbjDdZQAyw+3kEiIGDrJuYdWk6XwEAPevC50CXmZ0+3q0CAwEAAaN2MHQw - DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF + DgYDVR0PAQH/BAQDAgGmMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF MAMBAf8wHQYDVR0OBBYEFN0KfRluDdz4MNcFXcyH/6SRRiwXMB0GA1UdEQQWMBSC - Em1hc3Rlci5kYXRhd2lyZS5pbzANBgkqhkiG9w0BAQsFAAOCAQEAvtan3vxT2lHk - L0qoqHra54OhhDdBEEdQ1NngiyFY1He4HbHmAQi7MtMCDd8GwqKJsIhK9dZ9Q3Ux - Z0BvYjB7q9U2i5Fc+KmkbJMsL6gp+Pm9txNq9gDMHz9SaVMqFuI7UyJ0V+M2V7E1 - 6PWIyxW6MSq5fM6NEEkNgxgGO0479uw588FTc5p6mIfRjcpRMJEXBed3lymQ27+m - MNOM/NS6vGdL9ajCqaw/QUV/kdTSfmiMowIo6ClabzIO47qi66sm/gZkUJ2BcLbj - G3IqbkXudkWkvZ5XgZ6JnyjXg1ifR/1uaUYPcchO0JiWUu9KHwZiKCoQM+qECCgX - /4YZh3ot2w== + Em1hc3Rlci5kYXRhd2lyZS5pbzANBgkqhkiG9w0BAQsFAAOCAQEAWsa4HTRL97gZ + NmGeOrkyrLnB7k/EGS01iR/UyIP6BObn1ydZjKInFzrZQHBQSnqYRynMCCCR11O4 + ijJH7pUyfY8r6e3ebE8aU0oS6Y0YKQcDF6jIyFJDpEQIQNBBbrUoMFCuNKpjOuBI + 0LsTRLCc7TtJzbUukt1bhfOpqjh1Zdt/yvUQdnGSy6YxukJQ01n64w4RJlU6BCQY + oFwAYtDlMAjhX4gS1ZByZx5rZAsArO5tC9a/kE1IXDIv2IeluXcPro+V9jcr8bX6 + 6JVnpZi+8XyWWHV0LUtLYx7ZRFUMTf0QdBeh4jLHcKozFSXBk52AFJTfd1wVs7EX + 1hOoYbcTbQ== -----END CERTIFICATE----- """), privkey=strip(""" @@ -104,15 +104,15 @@ def strip(s: str) -> str: +cwyQo0e9ptBlaMrAOxKJcVx58LXP1Rr4+lctLe198kJ1Fw9vwBpQW8UdrSuMQdh 6lc+OUoEq7F2jJYuWzaaanAMDRiRt+p0jKeyAUp+ZfMhvqBxg76I/rehGANkaAHq tEJJrjyOjUmi/tafCjUCHGk5Xjp74EKakdn1nzqI1Uv7Xc421QIDAQABo3UwczAO - BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIw + BgNVHQ8BAf8EBAMCAaIwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIw ADAfBgNVHSMEGDAWgBTdCn0Zbg3c+DDXBV3Mh/+kkUYsFzAdBgNVHREEFjAUghJw - cmVzdG8uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAE1OJ+e2seALd1uO - qB3cQ2Fd2R+sm6pSkitngoaWlmAOsiunT3Qa1H+UWsZdBOv7RQ9JbgmRzsgAMKnR - PwnGzDcJ9miBs/qY1LpGEeIziL5/BZ1QimhTvsLzbmdMBdFpZtYNTfGMxuoI2p3R - r8pMcAb9P1EFZ8xVtf0KH6Wttq5Z8OHtvkE8W8u+I/FEjdwuu2Uurjx6AamS5Tw/ - HmM305OUzE7q0PrFsCBpkkIoc0KOIY0Nrx9B9y0mTRaWpvf0gri1NQuc+a4koFCD - zdOaXfE5YKLIQyYBhVnblHbHpPO7PXyC5UqEpHvTaECFi/6eR1BZVCrrgMxCc9s5 - J03slWw= + cmVzdG8uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAHvZMkyZPm9R1z6h + 455NbSZTFCBPGpfMXEDpvVjDz+kLjXx6vrclskuItRlduSkMftgQHYHQr1Qs11Q1 + tCI483BY40vLGkeio3WnpL384GDKSlYU78CXBvo/uBz32IcN7btvnG3EwbHxPU4m + az7OHcmqgAuQq7V4hn3Z46It61ElpJ/Mtum+IPeSpv/95z3ZJeQ+XC+9Z4mkeutp + APjVDc7O+dPl1nsKZiD0wDWqphS4g2hQgkUn6h2TaqUi+cAfLf0v36kx6Y6+VOWi + 4gpi/Fe5t6hpjGu2AiUQ4CGxUeAYVMl62WDefAFMjOL1nj0KeszCh9BcVKEg2LaD + MbHj/1Q= -----END CERTIFICATE----- """), privkey=strip(""" @@ -164,14 +164,14 @@ def strip(s: str) -> str: 6cubtPMGSPzpxhF7FJ+SEuWEzKSonWKa93rk4+ytIcuVZeWmdirZbpuP6Bel05Cu i9Vs6Qia68AQ5tQvsKQoWUkFSJANeY7WMzqEgt+BG0hKt658otVOAJdyFPEA96j7 6CFjS9VXxcD18BruPWdil/6gprQhc/XVRU4cUrOOqPmoKhtmDekLY6Cka9MCAwEA - AaNXMFUwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1Ud + AaNXMFUwDgYDVR0PAQH/BAQDAgGiMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1Ud EwEB/wQCMAAwIAYDVR0RBBkwF4IVcmF0ZWxpbWl0LmRhdGF3aXJlLmlvMA0GCSqG - SIb3DQEBCwUAA4IBAQCIZJnPY9NkOY0w8GaVDHrkHIREU8r/B2cv3dNn0k9ktvlC - 0rgUvg7ZDzsr7h+uWI5VgxED1KpnQbDcHMQL2Wk0+z5xJgP+wj1ueSCniJGeOUEH - zWZQ4rfs8jUFkBT+Is12YX+YEOGYP71+EzmbGK3glRfbI+NrtJuv+vpiZKcQzHfq - V3IpUKpEJ0o4XVUuBKtnVXcWrR+KlJQCY2vC5eSMstjgC5YKVBRiVqbyIGA/ThDq - BpKO3eeUmF2SWhIzCCgLq49iTaBpSzw7mFZdQsOTyXQVVppOmcjqTiF3j8FaVTE5 - WWblE/fD+ZXIPEMxs9te3T9/DIKDM8AyxoJ1Jh7K + SIb3DQEBCwUAA4IBAQC5RLq+PZsKcJVOGb1YZu6HuqwOYYmiHh1xSuUnHYFFXkBN + b/sKYL6re2eyMp6rpBkq/kVNjIcTMVbAVB/gFQWYw/pMwdofqcjBCAytx0zSphAD + j0TnRrDZRvqeTLGQHxZ8flrnRG/dQxY4FyhnHkOeLB9V4RUD0VhETqXYGWdTE9Gt + mXqQN5cT9f/lF+fiX2uaH7/64YTsUJBFcO7TbalcLC1I9R+z6JzGPfg2Ff7gXYnl + 5+mskjzbXGkjzsicklr7Nkji2VlsfOTCQudacamYj11D8YhYOzrqd0wh9DtAQD5a + 44RUfY8jf7fdqK4ZkaDhFXfQzE/iZ5WjVD6h5aAD -----END CERTIFICATE----- """), privkey=strip(""" @@ -225,15 +225,15 @@ def strip(s: str) -> str: jsypZKoaDLD+uNmRwrMu7GrHCiPtSEfd5Otw5UFcx2bFuFnbbAby1pjsu/FOwqHN vV4q3ZVIX9eshLKTj2/ODr61yWiLO8LDCBy3nUPoW5FXGCbhRnP/ev0TUvxhYDus TrokBvIXGX5xGbIIHSZ3U8REJP41iC06m7RdbkKEaF61pDyqOSTUtrNhAgMBAAGj - eTB3MA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB + eTB3MA4GA1UdDwEB/wQEAwIBojATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB Af8EAjAAMB8GA1UdIwQYMBaAFN0KfRluDdz4MNcFXcyH/6SRRiwXMCEGA1UdEQQa - MBiCFmFtYmFzc2Fkb3IuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAG2o - 7XEVqjnQdIwoKX/ergbQtPxkyYTLrGDU6qSoLqiwrfEYnUam/oEwTrJtgee56WbT - eVvemVFEgvSdUoPAW/oa05pZZx33B8kHdQn69DwVyCMdQotx71Izo46Bhk4VuxFo - J6mBbCUOqdetEDOyehxXUNJRn8VtpDLXanT16tlz3JuJDPBFAk8OvQ6Y65vumedS - EDpmx8pPi6amfuOPtZZqP60owXA6bv+icEdGrVpK0sErDwN8kj3fvMYzkVROPtZL - 4Z6y4bEGqhBtO+sdz3rTsh7+UOHC/CZgXgdDUK2An2DcjccWqZ0ENAOABEnNgGfA - SrCA0+AQjzKx/eJwDcg= + MBiCFmFtYmFzc2Fkb3IuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBABaq + IXeJmKPxudL0A3DXODY+hnadOtj7GPbQFR6ip8/Ja/we9dRpfsWO3+MeuxdezOtC + NWJbJVrK/5WGjYeymaivly4CigmUXsF0eSiVxDoeZ9DzIYj0FZpP9A3URWqO0fXM + d1ljn1f9vENUvrkMFVEwke/+qRCXcdGKKJPfDAQI+0BZ5NmUZOrLhzZJgPUtSZSE + TCKr+sLoOmiH8dW5uzVaCgssNlVexutgMmFWP4uZD2i+PtE+oIDIDVKuKeRnreCT + 1r/e4SLxS8diOQyrwXwn5dqPH713qjnuhk7fFRSa1aa6aFZvsBmqBEykBeVJtErI + BUI4H1BdGgQsOuX/nbk= -----END CERTIFICATE----- """), privkey=strip(""" @@ -285,14 +285,14 @@ def strip(s: str) -> str: KxA5nZkS14jAbD3l8EcZ+ALMlLGBynu2PYYthfZ9yJx8CxUYYPReSFXXQauovMDT wzqHBvY2WLkU+cEWyYCaBo4yAsA5P5q34ZRVXn7ePbidnguJJ/34REGqJj3/13dP 79qOtTB0NZQuajMvacEcdgkrN5rD5I+yPYckFrzGXkhObhxP4cMCAwEAAaNUMFIw - DgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC + DgYDVR0PAQH/BAQDAgGiMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC MAAwHQYDVR0RBBYwFIISdGxzLWNvbnRleHQtaG9zdC0yMA0GCSqGSIb3DQEBCwUA - A4IBAQAzG/5JEIFnh4+31pxdmHq7VawsPpLQzAWbRvZ4BG+6DI54jLGgU8ZDXIxw - +2F6SgPiyxWHD6UbQEWlWkp+qWs1D9ribWWuT/AnKDtg6G1aOTm5yygKtyrmb8p1 - MujfSE17y7AgOF/3FcegZwZuHZxlQqwJZD6yTCiQYWBczVhPKXvlXIi4SkhnHX1b - aRMRTWvHdNMclm8BA5zbTL8njhUCKkjNXSySjtyOc22f4K6A9GS3R1/fYDxKKgol - aqFfLS8P0/niK+fGyBE8Lp6P9g63hS60bAOYxraJbGieFqbtKZH2H9von7e6Do3N - c1ySbPcvlmzY90yzieRidmD4dNDb + A4IBAQAa3ny3H6mOkjHgT/2JuZ/B9UjQAlWWvCy4yXuOMHkf3NgB09pV83OAMACB + 47EPDdsQH4CLYFNxU7zPJOKLztcSu009U+wgBryCF5htsQV4PHOsBLD8VlaBZlCG + hJ48LMnAcIbubQv594KPcdpS/ZArHHBWrqG+UWFEo8BvuaZQHZaG05Jzazat9Zjs + yWnL/eozfULllHAHCG2zjCbVbmoJ6Bl4gWAfRM0/Iv+4AtxyF0Gpbe90p4rJgttU + r6cDMXyBph9J29kPgu4QuAcGInwYqmB5jLJB2FW1mOzg5eG4WW7ZFLDBvA9SnUQP + LCNz4+dspzQ8mkD5b+lhuHmY0ywd -----END CERTIFICATE----- """), privkey=strip(""" @@ -344,14 +344,14 @@ def strip(s: str) -> str: 0Oe+yve98yMCvmJOsdIuxExojaAdfEo0o9on0/iy0cereeiegxjLO8QM7uwTDqSH +4dApzvy2PwwUEhUUZrNRdyp7tdW36IzTi7KeCWnX2++2yPNQTt7N3x3SNHf6gj1 fjQ6jYXwGQ0bF95KV5HXbTMEIXRb3hDX2jYiX3cXNOapx5KIio0CAwEAAaNUMFIw - DgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC + DgYDVR0PAQH/BAQDAgGiMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC MAAwHQYDVR0RBBYwFIISdGxzLWNvbnRleHQtaG9zdC0xMA0GCSqGSIb3DQEBCwUA - A4IBAQAvdh1cc3sOkksjlSJYj8qRyC5kxFm79YTWga3zm8EsUz7I8gjULCfSeEmT - aEu6wjFJWTxMBulU0jbu3QVNinQcOqxTEAXdMaP2/2akHZv/u0QHFsOH1K5b9X6w - iszEJlwvH8pm0hczzQ9RNgCo8JcmPf+SWlhNsvIjXTaY4VaezoWd80US/90EZWEg - CU7vaeTtUXiIkDFbnvoHnBfKtzFVPozdRK92Um5TqYAQhTfoLy1lZ37ehLtEi3/r - YnZJ/kVvvj0xu0b3ABS4X7tdquIFpRH9TlHzytPoExDP0yK4rDTBReRltjmG7Mo8 - C5c5xkWcqYhep7KMpnkU02onYioc + A4IBAQA3835AL41oL1ESf5lkLp2TOnZ2luZTKN1gr85dXoiGokXPNNpDJn3l40hO + +yTWqF9uzlTJ1hP/KzfkqfDoF2Cn4YlWHY5uYbCgvlwzmsV/a7vCw9J6iXqjDjTZ + M07Z44ul0IoCOxMj9dPEeRZiOMSSO7rsANt3puEYbiVLkmjfZqkI+7HH8t4tTMPE + HuFujTGxpKH5di+pFnsIwcWOk8nV7r/aVr9R02U02tBkaGKT0RVcjKZND08l7Hue + 39dj1MiqRdOoc7R6oKwyFwASd922pLwTXmovV93gg4hJAXCw7m8a8GB1gPt710j0 + GTueVtWpvLIGu5TB23vCmQBgcmJP -----END CERTIFICATE----- """), privkey=strip(""" @@ -402,14 +402,14 @@ def strip(s: str) -> str: B23BggWq3kzlgCDRQ6xOdzC39OYm3HfdX7jyeGoEPOsFlsbVwNHAcO7FEShRhfnN VIG9vlYXogaSQPWoBu1HkfyO24rrLV0+FlhB6ddTjKi1JP5XSQAZX2zVRuocgFut 3pt0SX47MqkO2/+CSEjVD42RosnyTobFsfLQFEAbhxaM1QhfFVxcfQXHWfvJhuNA - m2P75gGj/vezqW1mXRpubFxqswIDAQABo0swSTAOBgNVHQ8BAf8EBAMCBaAwEwYD + m2P75gGj/vezqW1mXRpubFxqswIDAQABo0swSTAOBgNVHQ8BAf8EBAMCAaIwEwYD VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2Nh - bGhvc3QwDQYJKoZIhvcNAQELBQADggEBACGfmKYqlwHH7ZldKgHDrkK1obomlAU3 - 2WQ43DHhSUsausgrTDwuXQ+3K6KpcyDwCJBXBPDVtouawfSaMfqKg3bjo+ttPwey - xJsLvdk/U0djpBDJ4GwJFMYsPPBBwGkDFu0w1Xy4IEbVPmzOJ9Lnj8Uf+YIbWwqC - BcTvz0BmIcZtkMXn9EdHWjQ1g/DodRkd3yeZjRMQ7WvcvUasOhG9CFGFjsC4ZDbv - x2Pz354apCjhr2Dqvg21/9LQgm4zmJakw8HwagRv3eNunLXQYYBcPtfeEVgXyTz+ - G0w3VUUFGNg+m+Mu+CqS73826wC/xE4I/1pUdoKkU+AqqakggSEH6Wk= + bGhvc3QwDQYJKoZIhvcNAQELBQADggEBAAofUGpqfmJk9xDlnuLoBF+78rZhB48s + upY+KeHQWK3K3JyhJMjmEHeXAYA9uRJn+d3PQDHDGBUUiK6JEvKDLiC+6Y7DLpK7 + bA1TmBtfWB2SEYGo4IRN8tg/Wi7mlWKZx9GhiMBRLHsz8sCJSGJHGvlV85Hh7/tV + Tz3+AilX7LMwljF0l4pfe6sJPjHKzTFt9kkH7+QsgxJQPUctFy4Vt2f0VdpatbHV + eNA6OuaIk6RTwTSDfGP0aLOUs9YTITez2zG+bIDxL1jgULj2z5APVjsq97BJpAxP + zXNRz2CDAv3axyY2uO63k1NNSbJaFx4t+9QhfyzfBIEsmvx1k5W9k1g= -----END CERTIFICATE----- """), privkey=strip(""" @@ -470,15 +470,15 @@ def strip(s: str) -> str: Vmd9yuxGIeZlucno/TK4yicGW8awATH0JsvKfbEDf0drztgeFq8RynlkNxEH56Qw 6tLS1rJ6CRY5yyYImwtA3/zyZzSTsSgm7EMYhPg4JSf0wuLTY1uOjfvidfIwhCGu Qxy8h8Z0eAQl0YigXersY2FL4MULD6PqrK0CAwEAAaOBjjCBizAOBgNVHQ8BAf8E - BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBWBgNVHREE + BAMCAaIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBWBgNVHREE TzBNggxhLmRvbWFpbi5jb22CDGIuZG9tYWluLmNvbYIMKi5kb21haW4uY29tggls b2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD - ggEBADdqgP0oBxv/wcctrpgdhfgzurWbUFE3VD3/540WFLmA6OrE5OsN8q3AVz7M - 5XtQDBJDQR7I7cGrXXdnJuvTi7RhO2JduAW+oO+ZtRN1yUk4bzmVJzNRAKNGxsPZ - NGvPacZyEDwmIodqUUOXIQnmJyvKd7pHuQyPyNDZkuVyH/z7m0vutBXSm4DbZ/Ml - Osh6zGQ9OUwy+4wCFi+PGDnHnGr4FuKzv1ZpSGiycA3l8aUu4qZ7a8gLtHe8UXa+ - W2OKptRz/ACOsjKEQjCwWXjOV5/LJwtI2qAM/BUEY/GiHeJoNT515bt/sh7yC4Cn - pgEAi+5QCFXEtyrT2rtCGfzYZZY= + ggEBAEBAwBp7CynmgAaAMOVQCVC4+Uv4ea3R72YdK08s77Mh8WH40PZpumeiZiw2 + sIW6amB8L1jBFNvDvgsXQZFmVKAP9YQoIQ56GZkVe0V9QLJf2Grs0TrG8Jm8lfjo + AEfCnBah7zvR4ZLsNKHFEDINLq8Jtv8EUutzD/7tX47vjQ2Wz6BB86LI/DlKZwHL + dbHQrlMz6YsVGwfeyMvcfoUaesk1J/jFczZXWA5+93+SPhy4r2bi+qJEcOjMQrHH + bZNZImIIvzD6Osl7TwQf8AziWDlgEKhLrxEuRUeb15+HVXUx7XSPxPtHByRCozxA + PZHq6TsBZAaoCzAOP9uxC9JcQJA= -----END CERTIFICATE----- """), privkey=strip(""" @@ -529,14 +529,14 @@ def strip(s: str) -> str: Hfn6dWVgBXws8ykSX3r/A3A5+l2xnE81jtYPOr4rtyYUCJF60yQ/w7FvSfXZIO8l hc6qncfzFm1Pg8SZPi9hhoL7/AzmQ+iuqG624VuanKppFDx5yq10X6guH6faVpbf ErrHOIP0N7KcZZjl0bnQdZVlVzXMe+Sra8xWuGqfjMmcL2LkzvtdkTv2AC+8rbNM - iaa564JoTcj5znYDAgMBAAGjRzBFMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAK + iaa564JoTcj5znYDAgMBAAGjRzBFMA4GA1UdDwEB/wQEAwIBojATBgNVHSUEDDAK BggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBAGA1UdEQQJMAeCBWFjb29rMA0GCSqG - SIb3DQEBCwUAA4IBAQAdl8MopS5T3LSo50RPgf7+OiTsCy9V37QIGkctZTGK5s/9 - Xiv/XQ8r/wYs3RTMlE9IsjPHyTkQ01FxwUQTliSM+6fpnYaRfVLPZlYNnz1S4j1S - pxiM6LcD/Ld0M5faRjCxMmLWqgzrwD6bhV/AS4zmLW3QSHiI8W4L+0q6zp3/4u0D - lCR3ObLpjoZq0mLTHqJExGZAcioafgxzl5pwKy9YtUO3acYv8CuJOZzyqbpCKXTa - f+n5Tslk+UdHlBb+4cm8VkExnZ+2+AWPrujvUzy60GW1tCWswfz9ZDaM4lLO4CyU - 1KEHBiKzK5s/c774GnHhGus3SoPsSCqENImvZb/x + SIb3DQEBCwUAA4IBAQBPVI+USljo0ySYTnHxDG8yxQQ4VOHNuNTp7SGtOG9UWGSH + XpeQqQefOz5vWIYBRrSgwmc/RZ+2rQiOyNlrq5Dt0I/dc5a7PeDL1G4gNvsLpHtz + xYS3aIzqmLqT3jMA7y++8RX/CVH4KwICadFLSMZK2qV00sHTQ6fMGi1YVz0E8fw3 + bOzusjr5ax/lcgEEexhtE49Z+7kNrHDYCQ4c9JBU6W2un6jhfZ5mp1KuXLNiMxbN + cTXNk8q9anHFFASQ2o4RFMUaKTMXf5OtrZ41x/aGznJ6LM3OpPhRSITPD9Nhosja + uKD5HptKkoH+RW74nmDZbUPoOWl4OcG9EXvTm1PG -----END CERTIFICATE----- """), privkey=strip(""" diff --git a/python/tests/utils.py b/python/tests/utils.py index 7e9a073db3..bbc5d7ceaa 100644 --- a/python/tests/utils.py +++ b/python/tests/utils.py @@ -8,6 +8,8 @@ import time from collections import namedtuple from retry import retry +from OpenSSL import crypto +from base64 import b64encode import json import yaml @@ -233,3 +235,22 @@ def assert_valid_envoy_config(config_dict, v2=False): if p.returncode != 0: print(p.stdout) p.check_returncode() + + +def create_crl_pem_b64(issuerCert, issuerKey, revokedCerts): + when = b"20220516010101Z" + crl = crypto.CRL() + crl.set_lastUpdate(when) + + for revokedCert in revokedCerts: + clientCert = crypto.load_certificate(crypto.FILETYPE_PEM, bytes(revokedCert, "utf-8")) + r = crypto.Revoked() + r.set_serial(bytes('{:x}'.format(clientCert.get_serial_number()), "ascii")) + r.set_rev_date(when) + r.set_reason(None) + crl.add_revoked(r) + + cert = crypto.load_certificate(crypto.FILETYPE_PEM, bytes(issuerCert, "utf-8")) + key = crypto.load_privatekey(crypto.FILETYPE_PEM, bytes(issuerKey, "utf-8")) + crl.sign(cert, key, b"sha256") + return b64encode((crypto.dump_crl(crypto.FILETYPE_PEM, crl).decode("utf-8")+"\n").encode('utf-8')).decode('utf-8') diff --git a/tools/src/testcert-gen/main.go b/tools/src/testcert-gen/main.go index 31879e0eb1..55422cc5cc 100644 --- a/tools/src/testcert-gen/main.go +++ b/tools/src/testcert-gen/main.go @@ -273,7 +273,7 @@ func genCert(args CertArgs) ([]byte, error) { // If you ever extend this program to generate certs with non-RSA keys, be aware // that x509.KeyUsageKeyEncipherment is an RSA-specific thing. // https://github.com/golang/go/blob/go1.17.3/src/crypto/tls/generate_cert.go#L88-L93 - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCRLSign, ExtKeyUsage: []x509.ExtKeyUsage{}, // We'll adjust this below. BasicConstraintsValid: true, }