-
Notifications
You must be signed in to change notification settings - Fork 904
Implement a fast lookup for wildcard certificates #2815
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
base: main
Are you sure you want to change the base?
Implement a fast lookup for wildcard certificates #2815
Conversation
5540ac3 to
d979058
Compare
gfoidl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: there are quite a few blank lines that could be removed too.
| var charA = x[^i] & 0x5F; | ||
| var charB = y[^i] & 0x5F; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You compare it backwards?
Is it better then, to write the loop backwards and have normal index access?
| var length = Math.Min(x.Length, y.Length); | ||
|
|
||
| for (var i = 1; i <= length; i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the JIT can't elide the bound-check for indexing into the spans.
I think it's better to slice the spans to the common (= min) length and use one span in the loop, in order to elide at least one bound check (the JIT should handle the downward loop (see other comment) in a recent version).
Roughly:
| var length = Math.Min(x.Length, y.Length); | |
| for (var i = 1; i <= length; i++) | |
| if (x.Length < y.Length) | |
| { | |
| y = y.Slice(0, x.Length); | |
| } | |
| else if (y.Length < x.Length) | |
| { | |
| x = x.Slice(0, y.Length); | |
| } | |
| for (var i = x.Length; i >= 1; --i) |
| _hasBeenUpdated = true; | ||
| } | ||
|
|
||
| public X509Certificate2 GetCertificate(ConnectionContext connectionContext, string domainName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
domainName CAN BE NULL!
when SNI is not available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well poop, forgot about SNI. I would expect that everything supports SNI by now. But yes, get your point...sigh...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would suggest skipping certificate lookups and immediately fallback to the default cert when string.IsNullOrEmpty
you WILL get this traffic anytime someone runs ssllabs server test, or some other kind of auditing tool
or for the handful of people running this on their own internal network and connecting with some kind of weird legacy system
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to handle non SNI TLS hand shake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
If this pull also needs to work for the Kubernetes.Controller the following needs to be modified also note: i don't work for MS but i have this code running right now with those lines patched out. |
e56abc3 to
0d20eac
Compare
Thanks for that |
MihaZupan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there are some related build & test failures in CI
| if (!string.Equals(namespacedName.ToString(), _options.DefaultSslCertificate, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return; | ||
| _logger.LogInformation("Found secret `{NamespacedName}` to use as default certificate for HTTPS traffic", namespacedName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these if/else logs inverted? (note the !string.Equals)
| } | ||
| else | ||
| { | ||
| _logger.LogInformation("Found secret `{NamespacedName}` to use for HTTPS traffic", namespacedName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might result in a bunch of misleading logs (including warnings) since we're assuming all secrets are certs
yarp/src/Kubernetes.Controller/Certificates/CertificateHelper.cs
Lines 33 to 37 in e04fc5a
| if (cert == null || cert.Length == 0 || privateKey == null || privateKey.Length == 0) | |
| { | |
| _logger.LogWarning("TLS secret '{NamespacedName}' contains invalid data.", namespacedName); | |
| return null; | |
| } |
We should probably check that it's a relevant secret before logging.
| #nullable enable | ||
| namespace Yarp.Kubernetes.Controller.Certificates; | ||
|
|
||
| public abstract class ImmutableCertificateCache<TCert> where TCert : class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this internal.
Why does it need to be abstract and generic? We should be able to merge this and ImmutableX509CertificateCache into one type.
| // Poll every 10 seconds for updates to | ||
| while (!stoppingToken.IsCancellationRequested) | ||
| { | ||
| await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this can use PeriodicTimer instead
| if (_hasBeenUpdated) | ||
| { | ||
| _hasBeenUpdated = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to handle _hasBeenUpdated changes under a lock so that we don't accidentally miss the last update in a race condition
| } | ||
|
|
||
|
|
||
|
|
||
| protected abstract TCert? GetDefaultCertificate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | |
| protected abstract TCert? GetDefaultCertificate(); | |
| } | |
| protected abstract TCert? GetDefaultCertificate(); |
Please remove all the extra new lines (across all touched files)
| /// This allows us to use a Binary Search to achieve a suffix | ||
| /// search. | ||
| /// </summary> | ||
| private class DomainNameComparer : IComparer<WildCardDomain> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| private class DomainNameComparer : IComparer<WildCardDomain> | |
| private sealed class DomainNameComparer : IComparer<WildCardDomain> |
| var charA = x[index] & 0x5F; | ||
| var charB = y[index] & 0x5F; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it might corrupt various characters. If the intention is to normalize the case for only ASCII characters, these should first check that these are ASCII letters.
| } | ||
|
|
||
| } | ||
|
|
||
|
|
||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | |
| } | |
| } | |
| } | |
| } | |
| } |
| { | ||
| foreach (var domain in getDomains(certificate)) | ||
| { | ||
| if (domain.StartsWith("*.")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (domain.StartsWith("*.")) | |
| if (domain.StartsWith("*.", StringComparison.Ordinal)) |
No description provided.