Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add service UID as x509 extension to service server certs #12413

Merged
merged 1 commit into from
Jan 17, 2017

Conversation

smarterclayton
Copy link
Contributor

ElasticSearch SearchGuard has a requirement that an attribute of the
certificate be used to identify intracluster traffic. That value is
currently a registeredID as a SubjectAlternativeName, which is
questionable. This PR includes the service UID as an x509 extension
(using the new OpenShift OID arc we have been allocated) in order to
enable a caller to have an unforgeable value on the certificate they can
consult to identifier other cluster members. This also adds more
traceability to the certificate for future use.

[test]

@smarterclayton
Copy link
Contributor Author

@jcantrill the OID of the certificate extension is 1.3.6.1.4.1.2312.17.100.2.1 - the value should be an asn.1 PrintableString or UTF8String (you should check for both). Reading the value when ES starts up should be sufficient.

@smarterclayton
Copy link
Contributor Author

smarterclayton commented Jan 7, 2017 via email

@jcantrill
Copy link
Contributor

@smarterclayton how are we to consume this OID? Is it your intention we simply replace the existing one with the one here and it will be added as a SAN? Will it be some other attribute of the certificate for which we will need to modify the existing code which reads it?

@smarterclayton smarterclayton requested a review from liggitt January 9, 2017 14:10
@smarterclayton
Copy link
Contributor Author

smarterclayton commented Jan 9, 2017 via email

@richm
Copy link

richm commented Jan 9, 2017

@jcantrill You'll need to ensure that localValue != null and peerValue != null before doing the array equals. That is, both certs must have the oid extension value - if either of them are null, the function should return false.

@smarterclayton
Copy link
Contributor Author

What rich said, and yes, looks pretty close.

@tiran
Copy link
Contributor

tiran commented Jan 10, 2017

@richm and @jcantrill asked me to review your proposal. A custom X509v3 extension with a registered OID is much more sane than a RID general name in SAN. A registered ID in the SAN extension is rather uncommon and even revealed a bug in Python, https://bugs.python.org/issue27691.

Your proposal could even be in line with the CA/B forum's baseline requirements, https://cabforum.org/baseline-requirements-documents/ . Section 7.1.2.3 "Subscriber Certificate" does neither forbid nor explicitly allow custom X509v3 extensions. On the other hand section 7.1.2.4 "All Certificates" states that

All other fields and extensions MUST be set in accordance with RFC 5280. The CA SHALL NOT issue a Certificate that contains a keyUsage flag, extendedKeyUsage value, Certificate extension, or other data not specified in section 7.1.2.1, 7.1.2.2, or 7.1.2.3 unless the CA is aware of a reason for including the data in the Certificate.

If you make a CA aware of the reason for the presence of your custom X509v3 extension, then you might be able to get a publicly trusted EE cert from e.g. Lets Encrypt. I'm not entirely sure, though. In case publicly trusted EE certs are a use case for OpenShift, more investigation is required.

@richm
Copy link

richm commented Jan 10, 2017

I'm not entirely sure, though. In case publicly trusted EE certs are a use case for OpenShift

Good question. OpenShift in general, yes - I believe someone is already looking into Let's Encrypt integration. However, for this very particular and specific case - these certificates are only used for intra-cluster Elasticsearch-to-Elasticsearch communication - they are not used by external clients at all, and are not even used by intra-cluster services such as OpenShift itself, or fluentd/kibana/etc. So what exactly do we need to worry about in this case?

@smarterclayton
Copy link
Contributor Author

smarterclayton commented Jan 10, 2017 via email

// service UID as an x509 v3 extension to the server certificate.
func ServiceServerCertificateExtension(svc *kapi.Service) crypto.CertificateExtensionFunc {
return func(cert *x509.Certificate) error {
uid, err := asn1.Marshal(svc.UID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is showing up oddly when I dump the resulting cert:

        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Alternative Name: 
                DNS:foo.default.svc, DNS:foo.default.svc.cluster.local
            1.3.6.1.4.1.2312.17.100.2.1: 
                .$e8162fce-d88e-11e6-b7df-acbc32c1ca87

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nm, openssl is just odd

@smarterclayton
Copy link
Contributor Author

smarterclayton commented Jan 12, 2017 via email

@liggitt
Copy link
Contributor

liggitt commented Jan 12, 2017

yeah, dumping the asn.1 tree, it looks right:

SEQUENCE(2 elem)
  OBJECT IDENTIFIER1.3.6.1.4.1.2312.17.100.2.1
  OCTET STRING(1 elem)
    PrintableString e8162fce-d88e-11e6-b7df-acbc32c1ca87

openssl just renders it weirdly

// OpenShiftServerSigningServiceUIDOID is an x509 extension that is applied to server certificates generated for services
// representing the UID of the service this certificate was generated for. This value is not guaranteed to match the
// current service UID if the certificates are in the process of being rotated out. The value MUST be an asn.1 encoded
// UTF8 string.
Copy link
Contributor

@liggitt liggitt Jan 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go outputs the string as a PrintableString (not a UTF8 string) if the characters are in a limited range

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, I thought I updated this comment. Not sure where it went.

sets.NewString(dnsName, fqDNSName),
certificateLifetime,
extensions.ServiceServerCertificateExtension(&kapi.Service{
ObjectMeta: kapi.ObjectMeta{UID: types.UID(secret.Annotations[ServiceUIDAnnotation])},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hrmm, I really don't want to take the annotation's word for it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we taking the annotation's word for it on the secret here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the service name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, which doesn't seem great. I expected at least verification of the data on the service object itself

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deads2k can you say why you didn't re-look up the service and verify the both way here? Is it happening earlier?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deads2k can you say why you didn't re-look up the service and verify the both way here? Is it happening earlier?

Previously, the name was signed for, but the UID was not signed for. The worst someone could do is make a set of non-sensical metadata that made the controller do nothing. Now that you're signing this information, you need a stronger guarantee.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To state it a different way. By signing for this information, you're asserting that the information is true, you're asserting that no one can trick the signer into signing arbitrary data, and you need to be sure that if this cert is stolen the information it asserts cannot be used by someone else without significant control of the rest of system (say owning DNS and IP routing).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is fixed now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, all this worked before. The regenerateNeeded was already checking uid equality against the annotation. I changed the code to return the service that was already checked, and so this protected already.

OpenShiftServerSigningServiceOID = oid(OpenShiftServerSigningOID, 2)
// OpenShiftServerSigningServiceUIDOID is an x509 extension that is applied to server certificates generated for services
// representing the UID of the service this certificate was generated for. This value is not guaranteed to match the
// current service UID if the certificates are in the process of being rotated out. The value MUST be an asn.1 encoded
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this mean? cert rotation because a service was deleted/recreated? or rotation for some other reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was trying to imply that you are not guaranteed to have it be equal immediately. And right now, the SSCC doesn't regenerate certs in all cases (deletion of the secret) so I wanted to add weasel words.

// if the extension attempt failed.
type CertificateExtensionFunc func(*x509.Certificate) error

func (ca *CA) MakeServerCert(hostnames sets.String, expireDays int, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"bring my by big hammer". This seems excessively broad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is generic code, and cert extension really has nothing to do with the common spot. Also this is the normal pattern for extension in Go now when you're doing object customization.

@smarterclayton smarterclayton force-pushed the x509_uid_extension branch 2 times, most recently from 62084c9 to 4a5e6de Compare January 12, 2017 21:39
if err != nil || !exists {
return nil
}
service := serviceObj.(*kapi.Service)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check the service references this secret as its serving cert secret?

}
serviceObj, exists, err := sc.serviceCache.GetByKey(secret.Namespace + "/" + secret.Annotations[ServiceNameAnnotation])
if err != nil || !exists {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens in this case? retry later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the resync interval or on the next change to the secret.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crap... I just realized all this logic was already in regenerate. Simplifying this again.

@smarterclayton smarterclayton force-pushed the x509_uid_extension branch 2 times, most recently from 744e3b0 to 1c48f88 Compare January 12, 2017 22:26
@smarterclayton
Copy link
Contributor Author

Any more comments?

}

service := serviceObj.(*kapi.Service)
if secret.Annotations[ServiceUIDAnnotation] != string(service.UID) {
return false
return false, nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double check the back-reference (that the service still has an annotation asking for a cert to be put in that secret)

@liggitt
Copy link
Contributor

liggitt commented Jan 16, 2017

check the back-reference, then LGTM

ElasticSearch SearchGuard has a requirement that an attribute of the
certificate be used to identify intracluster traffic. That value is
currently a registeredID as a SubjectAlternativeName, which is
questionable. This PR includes the service UID as an x509 extension
(using the new OpenShift OID arc we have been allocated) in order to
enable a caller to have an unforgeable value on the certificate they can
consult to identifier other cluster members. This also adds more
traceability to the certificate for future use.
@smarterclayton
Copy link
Contributor Author

Back-ref-i-checked [merge]

@openshift-bot
Copy link
Contributor

Evaluated for origin test up to 778391d

@openshift-bot
Copy link
Contributor

Evaluated for origin merge up to 778391d

@openshift-bot
Copy link
Contributor

continuous-integration/openshift-jenkins/test SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pr_origin/12925/) (Base Commit: a3e2498)

@openshift-bot
Copy link
Contributor

openshift-bot commented Jan 17, 2017

continuous-integration/openshift-jenkins/merge SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pr_origin/12932/) (Base Commit: 0b38fe0) (Image: devenv-rhel7_5695)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants