- Notifications
You must be signed in to change notification settings - Fork 25.6k
Support SAN/dnsName for restricted trust #91946
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
Conversation
Pinging @elastic/es-security (Team:Security) |
Hi @jakelandis, I've created a changelog YAML for you. |
@@ -0,0 +1,58 @@ | |||
# Create the nodes certificates |
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.
Here is the readme to create these ... it is kinda buried in the noise.
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, with a couple of things we can follow up.
libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java Outdated Show resolved Hide resolved
values.addAll(dnsNames); | ||
} | ||
if (x509Fields.contains(SAN_OTHER_COMMON.toLowerCase(Locale.ROOT))) { | ||
Set<String> otherNames = getSubjectAlternativeNames(certificate).stream() |
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.
Set<String> otherNames = getSubjectAlternativeNames(certificate).stream() | |
Set<String> otherNames = sans.stream() |
| ||
public static final Function<String, Setting<List<String>>> TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE = key -> Setting.listSetting( | ||
key, | ||
List.of("subjectAltName.otherName.commonName"), |
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.
List.of("subjectAltName.otherName.commonName"), | |
List.of(RestrictedTrustConfig.SAN_OTHER_COMMON), |
?
TRUST_RESTRICTIONS_PATH_TEMPLATE | ||
); | ||
| ||
public static final Function<String, Setting<List<String>>> TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE = key -> Setting.listSetting( |
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.
The existing SSL config code mostly uses enums for these sorts of settings (e.g. clientAuth
, verificationMode
). Is it intentional that this would be different?
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.
The dotted names make sense to me, just in case we get requests to parse CN from other parts of a cert in the future. For example:
- subjectName.commonName
- issuerName.commonName
- subjectAltName.otherName.commonName
- issuerAltName.otherName.commonName
- subjectAltName.directoryName
- issuerAltName.directoryName
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.
The existing SSL config code mostly uses enums for these sorts of settings (e.g. clientAuth, verificationMode). Is it intentional that this would be different?
The settings infrastructure does not allows you to define a default that is not a list of strings. The generic listSettings only support a Setting<List<T>> fallbackSetting
, which is very different from a List<T> defaultList
(which is what we would need). I see there is support in the SslConfigurationLoader
via the resolveListSetting
abstraction. However, I found that abstraction very confusing since IMO it breaks the contract of how to define settings and where the defaults are derived. Rather than deriving the default from the setting itself , it is derived explicitly at run time via a specialized method and the default is defined in business class not the setting itself. The setting itself must be defined to have a default value of null
which is arguably wrong since it violates the settings contract. Since the default value must be defined as null (when it is not null), you can not simply pass around the setting and expect to get back the correct default. Further, that abstraction is only in 8.x which would make the backporting even more difficult. I personally found adding the setting to be by far the most complicated and frustrating piece of this entire change mostly due to the abstraction and mismatched expectations of how you define a default and read a default value (it uniquely different from how any other settings work). I chose to better honor the setting contract and define the default as the default in the setting. Enums are nice but we need better support from the setting infrastructure to model the default as a list of enums (I could model the values as an enum as a wrapper around the strings, but that is kinda pointless). Long term, I would like to revisit this abstractions and make the other settings that default as non strings better support defaults as part of the setting definition itself.
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.
The code in ssl-config
is complicated by the fact that libs can't depend on Setting
(the Setting class is in core, and we can't have a dependency from ssl-config back to core).
So all the "Settings" in ssl-config have to be implemented in a very manual way which includes having to handle default values with explicit code instead of a default value. Technically it would have been possible to workaround it in different ways (essentially by duplicating half of theSetting
class into ssl-config) but we made the choice to just handle null/empty values in SslConfigurationLoader
. None of that has anything to do with enums - the code works the same way for string and enums, it's just a consequence of the fact that Setting
& Settings
have not been extracted into a standalone library.
Setting
always treats default values as String
so that things like _cluster/settings?include_defaults=true
can work (which it doesn't for SSL settings because we can't use Setting
, but the SSL settings are filtered anyway, so it wouldn't show anything even if we did register a default). The idea is that every default value for a setting can be represented in the same format that an admin would use to define that setting. So, even if the setting is an integer (see intSetting
) the default value is a String
which will then be parsed back into an Integer
when read.
That doesn't prevent using an enum for a Setting
type - see for example LdapUserSearchSessionFactorySettings.SEARCH_SCOPE
or LicenseService.ALLOWED_LICENSE_TYPES_SETTING
, it just means that the default values need to be converted into a String
(or List<String>
).
The benefit is that all the code that checks whether a particular x509_field is configured deals with real enum objects rather than strings pretending to be enums - all the case conversion / parsing code lives in the Setting
and anything that uses the Setting value can do a simple contains
(or for single-value settings, switch
).
I'm not especially fussed about whether we change it here, but I do think that it is an overall benefit if Setting
objects that have an enumerable list of allowed strings are implemented using real enum
objects.
Perhaps we should add enumSetting
and enumListSetting
to Setting
to make it easier to model it that way.
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 have updated the code to better conform to the existing settings and use an enumeration. I had to exposed the default value as a final static public variable used in both the buisness class and the setting itself which alieviates my concerns about the differences in the default w.r.t. to the contract. (FWIW, the settings code requires a non-null default value for list settings). Change is over at #91983.
final CertificateTrustRestrictions restrictions = new CertificateTrustRestrictions(List.of("node.trusted")); | ||
final RestrictedTrustManager trustManager = new RestrictedTrustManager(baseTrustManager, restrictions, Set.of(SAN_OTHER_COMMON)); | ||
assertTrusted(trustManager, "onlyOtherName"); | ||
} |
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 don't think we have any tests that verify that (for example) the SAN_DNS
names aren't used if the field
is set to SAN_OTHER_COMMON
(the default.
That is, with a cert that was generated from:
- ip: 50.100.150.200 - dns: search.example.com - cn: instance03.cluster02.elasticsearch
We should accept the cert with either:
trust.subject_name=*.cluster02.elasticsearch
,x509_fields=[ "subjectAltName.otherName.commonName" ]
(that is, the default value forx509_fields
)trust.subject_name=*.example.com
,x509_fields=[ "subjectAltName.dnsName" ]
But none of these:
trust.subject_name=*.cluster02.elasticsearch
,x509_fields=[ "subjectAltName.dnsName" ]
trust.subject_name=*.example.com
,x509_fields=[ "subjectAltName.otherName.commonName" ]
trust.subject_name=*.150.200
,x509_fields=[ "subjectAltName.otherName.commonName", "subjectAltName.dnsName" ]
I can see that the code will work correctly in these cases, but I can't see tests for it.
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.
The randomly picked x509_fields here should provide coverage that SAN_DNS
names aren't used if the field is set to SAN_OTHER_COMMON
(and vice-versa). Since values are identical on the certificate, there is a chance that we could be reading from the wrong value if something else is buggy (but the other tests should cover that scenario). However it's pretty easy to add an explicit test for this case and will do so on a follow up PR.
@elasticmachine run elasticsearch-ci/bwc please
|
@elasticmachine run elasticsearch-ci/bwc another time please. |
X509Certificate[] certs = CertParsingUtils.readX509Certificates(Collections.singletonList(cert)); | ||
assertTrue(certs[0].getSubjectAlternativeNames().stream().filter(pair -> (Integer) pair.get(0) == 0).findAny().isEmpty()); | ||
certificates.put("onlyDns", certs); | ||
List<String> validDnsNames = randomNonEmptySubsetOf( |
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.
Missing dnsName wildcard test coverage? Examples:
dnsName="*.localdomain"
withtrust_restrictions=[ "*.localdomain" ]
dnsName="*.example.com"
withtrust_restrictions=[ "*.com" ]
dnsName="*.example.com"
withtrust_restrictions=[ "*.example.*" ]
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.
Missing dnsName ACE encoding test for I18N mentioned in RFC 5280 Section 7.2.
IA5String is limited to the set of ASCII characters. To accommodate internationalized domain names in the current structure, conforming implementations MUST convert internationalized domain names to the ASCII Compatible Encoding (ACE) format as specified in Section 4 of RFC 3490 before storage in the dNSName field.
If design intent is to defer I18N dnsName support, maybe add a test to verify ACE encoded values are rejected or ignored.
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.
Is design intent for trust_restrictions to do greedy matching or lazy matching?
SAN dnsName matching is lazy (i.e. one subdomain) per HTTP/TLS RFC 2818 Section 3.1.
Matching is performed using the matching rules specified by RFC2459. If more than one identity of a given type is present in the certificate (e.g., more than one dNSName name, a match in any one of the set is considered acceptable.) Names may contain the wildcard character * which is considered to match any single domain name component or component fragment. E.g., *.a.com matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but not bar.com.
It looks like SAN dnsName logic reuses same code as SAN otherName. I can't tell if SAN otherName design intent was to do lazy or greedy matching based on Elastic Cloud doc examples. Would it be a good idea to add some more tests to make design intent more clear?
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.
The match aligns with our other usage of how match strings. We support wildcard expressions and lucene regex's. Both are string matches, not aware of domain names. The docs need to be updated (they need it before and after this PR) and will be handled in a different PR (likely in a different repository).
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.
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. Missing dnsName wildcard test coverage?
This commit extends the TLS restricted trust model to allow reading from alternative fields from the X509 certificate. Prior to this commit the only supported (hard coded) value that could be used with restricted trust is the SAN/otherName/CN value. This commit introduces support to read from other fields from the X509 certificate. This commit also introduces support to read from SAN/dnsName if configured. Any fields read from the certificate will be used to match against the restricted trust file and if any of the values match to the restricted trust file, then restricted trust is allowed. Only if none of the values match then the restricted trust denied. SAN/otherName/CN is the default, and SAN/dnsName can be used in addition or in place of SAN/otherName/CN. The possible configuration values are: *.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"] To help support testing, all of the existing certificates have been updated to include a SAN/dnsName that matches the SAN/otherName/CN. This allows the tests to randomize which field(s) are used to match for restricted trust. This also has the side effect of making this commit larger than expected in terms of lines of change. A readme has been included with copy-able commands to recreate the certificates as needed. Additionally, a CCS REST test has been introduced that uses the restricted trust. To support this new CCS REST test the private keys for the test certificates are also included in this commit as well as the gradle configuration needed to share those certificates across projects.
This commit extends the TLS restricted trust model to allow reading from alternative fields from the X509 certificate. Prior to this commit the only supported (hard coded) value that could be used with restricted trust is the SAN/otherName/CN value. This commit introduces support to read from other fields from the X509 certificate. This commit also introduces support to read from SAN/dnsName if configured. Any fields read from the certificate will be used to match against the restricted trust file and if any of the values match to the restricted trust file, then restricted trust is allowed. Only if none of the values match then the restricted trust denied. SAN/otherName/CN is the default, and SAN/dnsName can be used in addition or in place of SAN/otherName/CN. The possible configuration values are: `*.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"]` To help support testing, all of the existing certificates have been updated to include a SAN/dnsName that matches the SAN/otherName/CN. This allows the tests to randomize which field(s) are used to match for restricted trust. This also has the side effect of making this commit larger than expected in terms of lines of change. A readme has been included with copy-able commands to recreate the certificates as needed. Additionally, a CCS REST test has been introduced that uses the restricted trust. To support this new CCS REST test the private keys for the test certificates are also included in this commit as well as the gradle configuration needed to share those certificates across projects.
This commit extends the TLS restricted trust model to allow reading from alternative fields from the X509 certificate. Prior to this commit the only supported (hard coded) value that could be used with restricted trust is the SAN/otherName/CN value. This commit introduces support to read from other fields from the X509 certificate. This commit also introduces support to read from SAN/dnsName if configured. Any fields read from the certificate will be used to match against the restricted trust file and if any of the values match to the restricted trust file, then restricted trust is allowed. Only if none of the values match then the restricted trust denied. SAN/otherName/CN is the default, and SAN/dnsName can be used in addition or in place of SAN/otherName/CN. The possible configuration values are: *.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"] To help support testing, all of the existing certificates have been updated to include a SAN/dnsName that matches the SAN/otherName/CN. This allows the tests to randomize which field(s) are used to match for restricted trust. This also has the side effect of making this commit larger than expected in terms of lines of change. A readme has been included with copy-able commands to recreate the certificates as needed. Additionally, a CCS REST test has been introduced that uses the restricted trust. To support this new CCS REST test the private keys for the test certificates are also included in this commit as well as the gradle configuration needed to share those certificates across projects.
This commit extends the TLS restricted trust model to allow reading from alternative fields from the X509 certificate. Prior to this commit the only supported (hard coded) value that could be used with restricted trust is the SAN/otherName/CN value. This commit introduces support to read from other fields from the X509 certificate. This commit also introduces support to read from SAN/dnsName if configured. Any fields read from the certificate will be used to match against the restricted trust file and if any of the values match to the restricted trust file, then restricted trust is allowed. Only if none of the values match then the restricted trust denied. SAN/otherName/CN is the default, and SAN/dnsName can be used in addition or in place of SAN/otherName/CN. The possible configuration values are: ``` *.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"] ``` To help support testing, all of the existing certificates have been updated to include a SAN/dnsName that matches the SAN/otherName/CN. This allows the tests to randomize which field(s) are used to match for restricted trust. This also has the side effect of making this commit larger than expected in terms of lines of change. A readme has been included with copy-able commands to recreate the certificates as needed. Additionally, a CCS REST test has been introduced that uses the restricted trust. To support this new CCS REST test the private keys for the test certificates are also included in this commit as well as the gradle configuration needed to share those certificates across projects.
Follow up PR created : #91983 |
This commit extends the TLS restricted trust model to allow reading from alternative fields from the X509 certificate. Prior to this commit the only supported (hard coded) value that could be used with restricted trust is the SAN/otherName/CN value. This commit introduces support to read from other fields from the X509 certificate. This commit also introduces support to read from SAN/dnsName if configured. Any fields read from the certificate will be used to match against the restricted trust file and if any of the values match to the restricted trust file, then restricted trust is allowed. Only if none of the values match then the restricted trust denied. SAN/otherName/CN is the default, and SAN/dnsName can be used in addition or in place of SAN/otherName/CN. The possible configuration values are: `*.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"]` To help support testing, all of the existing certificates have been updated to include a SAN/dnsName that matches the SAN/otherName/CN. This allows the tests to randomize which field(s) are used to match for restricted trust. This also has the side effect of making this commit larger than expected in terms of lines of change. A readme has been included with copy-able commands to recreate the certificates as needed. Additionally, a CCS REST test has been introduced that uses the restricted trust. To support this new CCS REST test the private keys for the test certificates are also included in this commit as well as the gradle configuration needed to share those certificates across projects. Backport of: elastic#91971, elastic#91946
A follow up to #91946 with the minor requested changes. Changes included here are: * reuse of variables * additional unit test * convert to use enumeration instead of set of strings
…#91983) A follow up to elastic#91946 with the minor requested changes. Changes included here are: * reuse of variables * additional unit test * convert to use enumeration instead of set of strings
This commit extends the TLS restricted trust model to allow reading from
alternative fields from the X509 certificate. Prior to this commit the only
supported (hard coded) value that could be used with restricted trust
is the SAN/otherName/CN value. This commit introduces support to read
from other fields from the X509 certificate. This commit also introduces
support to read from SAN/dnsName if configured. Any fields read from the
certificate will be used to match against the restricted trust file and if any
of the values match to the restricted trust file, then restricted trust is allowed.
Only if none of the values match then the restricted trust denied.
SAN/otherName/CN is the default, and SAN/dnsName can be used in addition
or in place of SAN/otherName/CN. The possible configuration values are:
*.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"]
To help support testing, all of the existing certificates have been updated
to include a SAN/dnsName that matches the SAN/otherName/CN. This
allows the tests to randomize which field(s) are used to match for restricted trust.
This also has the side effect of making this commit larger than expected in
terms of lines of change. A readme has been included with copy-able commands
to recreate the certificates as needed.
Additionally, a CCS REST test has been introduced that uses the restricted trust.
To support this new CCS REST test the private keys for the test certificates are also
included in this commit as well as the gradle configuration needed to share those
certificates across projects.