Cleaning up TLS server verify options#4414
Conversation
When more options are added, it will be increasingly difficult to allow user to choose which option combinations to use. For example, when there are A, B, C and ALL, with ALL equivalent to A + B + C, how does user choose A + B only? Does that mean we need to start adding AB, AC, BC along with ALL? In classic programming world, people use bit wise OR to solve the problem. I wonder if 1) my concern is actually a concern (how likely would C exist); and if yes 2) can we improve the config syntax to make it more future proof? |
|
I thought the policy entry would be a list. |
|
The plan is that the properties entry would be a list. Just didn't need to be yet for two properties, so I didn't bother with that parsing logic yet. In the future if the properties that can be checked by the core Traffic Server logic are SIGNATURE, NAME, FOO, then valid values for proxy.config.ssl.client.verify.server.properties would include Internally, I'm assigning masks to the properties (though with two values the numerical values are the same as a straight enumeration). So in the foo case, FOO=0x4 and ALL=0x7. So we can add bit checks like if (my_properties & FOO) { I completely agree that the internal implementation should involve bitmasks, and that is how it is written in this PR. But I am unclear on how you are suggesting that should be revealed through the configuration interface. |
|
It might be reasonable to leave it for now and wait for the YAML conversion where handling lists will be easier. |
|
|
||
| #. Enable or disable, per your security policy, server SSL certificate | ||
| verification using :ts:cv:`proxy.config.ssl.client.verify.server` in | ||
| verification using :ts:cv:`proxy.config.ssl.client.verify.serve.policyr` in |
There was a problem hiding this comment.
Looks to be a typo "policyr". Also, since this is supposed to be a compatible change we should not remove the documentation for "proxy.config.ssl.client.verify.server".
There was a problem hiding this comment.
I'm adding back the docs of the original settings in the file-based documentation with notes about the old version being deprecated. This file is a to-do list to set up TLS security. I think it is ok to only talk about the new interface here.
There was a problem hiding this comment.
Fixing typo. I am adding back the docs on the file-based documentations. Since this seems to be a how-to list, I think it is ok to just discuss the new mechanism here.
| * Set to DISABLED, PERFMISSIVE, or ENFORCED | ||
| * Controls how the server certificate verification is handled | ||
| */ | ||
| uint8_t verifyServerPolicy = 0; |
There was a problem hiding this comment.
An enum class would would better here and you can specify that it is of size uint8_t.
There was a problem hiding this comment.
Yep. Updating. There was already an enum around but for some reason we were storing as the uint8_t. Fixing this eliminated a fair amount of casting,
| int8_t verifyLevel = 0; // whether to verify the next hop | ||
| SSL_CTX *ctx = nullptr; // ctx generated off the certificate to present to this server | ||
| const char *name = nullptr; // name of the server | ||
| int8_t verifyServerPolicy = 0; // whether to verify the next hop |
There was a problem hiding this comment.
Again, I think an enum class would be better.
e665f1c to
de7a416
Compare
|
Just pushed up another set of changes that includes a test that exercises the case of a plugin with code on the TS_EVENT_SSL_SERVER_VERIFY_HOOK. This code also changes the signature of the TSVConnReenable() to add an event argument. The plugin sets TS_EVENT_VCONN_ERROR in the TSVConnReenable when it determines there is a problem with the origin certificate and the TLS handshake again. This is another API change, and perhaps should be moved into a separate PR, so the rest of the changes in this PR can be made in the 8.x branch with some hope of backwards compatibility. |
de7a416 to
159af65
Compare
|
Pushed up new commit to fix the plugin examples and address @bryancall 's comments |
|
|
||
| int | ||
| CB_server_verify(TSCont cont, TSEvent event, void *edata) | ||
| { |
There was a problem hiding this comment.
@CrendKing here is the test plugin callback to take over for the origin certificate verification. Since this is a canned test, it is kind of a silly test case. If the sni name matches one of the preconfigured "bad name", the plugin reenables with an error which will cause the cert verification to fail.
In the test, I have the base case configured to check no properties. But if you had a real certificate set up, you could set verify_server_properties to SIGNATURE.
Does this meet your use case?
There was a problem hiding this comment.
The only part not immediately apparent when comparing to the OpenSSL SSL_set_verify() approach is the assurance of the validity of the peer certificate. From what I understand the code, if signature_ok is 0, the hook callback will not be called at all, correct? So we should be able to assume that the certificate is good at the moment.
There was a problem hiding this comment.
If you have server.verify.properties set to include SIGNATURE, then a failure in the signature check will short cut the verification process with a failure and not call the hook.
The hook will be called if the built in checks were enabled and succeeded.
159af65 to
5ae5141
Compare
|
Splitting the TSVConnReenable change and related verify plugin test into a separate PR. So the API changes in this PR should be backwards compatible. Will set up another PR with the verify plugin test changes. |
5ae5141 to
e85b293
Compare
|
|
||
| You can override this global setting on a per domain basis in the ssl_servername.yaml file using the :ref:`verify_server_policy attribute<override-verify-server-policy>`. | ||
|
|
||
| :DISABLED: Server Certificate will not be verified |
There was a problem hiding this comment.
You might reformat this as
:code:`DISABLED`
Server certificate ...
:code:`PERMISSIVE`
Server certificate will ...
| .. ts:cv:: CONFIG proxy.config.ssl.client.verify.server.properties STRING ALL | ||
| :reloadable: | ||
|
|
||
| Configures Traffic Server for what the default verify callback should check during origin server verification. |
There was a problem hiding this comment.
It's better to use |TS| for "Traffic Server".
| :ALL: Check both the signature and the name. | ||
|
|
||
| .. ts:cv:: CONFIG proxy.config.ssl.client.verify.server INT 0 | ||
| :reloadable |
There was a problem hiding this comment.
":reloadable:"
Isn't there a ":deprecated:" also?
| If :code:`false` then HTTP/2 is removed from | ||
| the valid next protocol list. It is not an error to set this to :code:`false` | ||
| for proxy ports on which HTTP/2 is not enabled. | ||
| client_cert The client certificate to use for the outbound connection. |
There was a problem hiding this comment.
Do you mean the path or file containing the client certificate? Or is this the actual certificate?
|
|
||
| In addition ``verify_server_properties`` specifies what Traffic Server will check when performing the verification. | ||
|
|
||
| :code:`NONE` |
There was a problem hiding this comment.
This is how the other enumerations should be formatted. See my previous comments.
| @@ -92,18 +103,34 @@ verify_callback(int preverify_ok, X509_STORE_CTX *ctx) | |||
| if (validate_hostname(cert, sni_name, false, &matched_name)) { | |||
There was a problem hiding this comment.
Must this allocate a string?
There was a problem hiding this comment.
In the bowel's of the validate_hostname it allocates space for a copy of the peer name if there is a match of a ASN string generated from mucking about in the X509 cert. Afraid that this copying is necessary given out the openssl interface for grubbing about in the cert. The code in question is in src/tscore/X509HostnameValidator.cc.
If we passed in a nullptr, no string would be created.
| return preverify_ok; | ||
| } else { // Name validation failed | ||
| // Get the server address if we did't already compute it | ||
| if (netvc->options.sni_servername) { |
There was a problem hiding this comment.
Isn't this test backwards?
There was a problem hiding this comment.
You mean stylistically? Having the error case as the else?
Or you mean filling in buff? If the sni_servername was not null, we already filled in the buff. In fast sni_name and buff will be pointing at the same memory.
| } | ||
| } | ||
|
|
||
| if (!verify_backward_compatible) { |
There was a problem hiding this comment.
Why check this flag? I would think you should just check the source - if both the new and old are set, the new should take precedence.
There was a problem hiding this comment.
Oh, we don't want to always use the old if it is set?
| if (!verify_backward_compatible) { | ||
| char *verify_server = nullptr; | ||
| REC_ReadConfigStringAlloc(verify_server, "proxy.config.ssl.client.verify.server.policy"); | ||
| if (strcmp(verify_server, "DISABLED") == 0) { |
There was a problem hiding this comment.
We're defining the values as upper case. Should we allow both?
| constexpr char TS_verify_origin_server[] = "verify_origin_server"; | ||
| constexpr char TS_client_cert[] = "client_cert"; | ||
| constexpr char TS_ip_allow[] = "ip_allow"; | ||
| #define TSDECL(id) constexpr char TS_##id[] = #id |
There was a problem hiding this comment.
How could you do this to me?
There was a problem hiding this comment.
This was @ywkaras's suggestion. Eliminates errors in double typing
e85b293 to
38ed35d
Compare
|
Updated to address @SolidWallOfCode's comments. Also rebased against master to pick up conflicting merged changes. |
My previous commit updates addressed Bryan's concerns.
Split verify.server into policy and propoerties components.
38ed35d to
db825d3
Compare
|
Updated to rebase against updated master |
|
This PR also needs "Fix chained server cert verify #4497" to fix an issue |
|
This has merge conflicts, and we'll need a new 8.1.x PR. Also, feel free to recommend / suggest other PRs that this depends on, such that it makes the back porting easier. |
Split verify.server into policy and properties components. As discussed on the dev mailing list on Oct 10, 2018. Repeated below
"Currently there is a records.config entry, proxy.config.ssl.client.verify.server, which can be set to 0 (no verify), 1 (strict verify), or 2 (check but only log).
This global setting can be overridden in the ssl_server_name.yaml file using the verify_origin_server parameter which can be set to NONE (no verify), MODERATE (check but only log), and STRICT (verify and enforce).
In PR #4013 CrendKing identified a need to allow for the openssl signature checking but bypassing the Traffic Server verification that the requested SNI is in the certificate. They have their own logic for verifying the validity of the name in the cert and could implement that in a callback on the TS_SSL_SERVER_VERIFY_HOOK.
With another option to our enumeration, I propose rearranging our verify options as follows.
Break the configuration into a policy component and a properties component. In records.config have proxy.config.ssl.client.verify.server.policy and proxy.config.ssl.client.verify.server.properties. The policy entry is one of DISABLED, PERMISSIVE, and ENFORCED (following the selinux nomenclature). The properties entry is a list of the following: SIGNATURE, NAME, or ALL. As we come across more things to check in the future the properties list could be expanded.
A similar change would be made for the ssl_server_name.yaml attributes."