diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h index 6a25f7ce818..120aee4aec7 100644 --- a/iocore/net/P_SSLConfig.h +++ b/iocore/net/P_SSLConfig.h @@ -173,6 +173,8 @@ struct SSLConfigParams : public ConfigInfo { void cleanup(); void reset(); void SSLConfigInit(swoc::IPRangeSet *global); + void SetServerPolicy(const char *); + void SetServerPolicyProperties(const char *); private: // c_str() of string passed to in-progess call to updateCTX(). diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc index a55e34ac2b0..8154ecd448f 100644 --- a/iocore/net/SSLConfig.cc +++ b/iocore/net/SSLConfig.cc @@ -189,6 +189,67 @@ set_paths_helper(const char *path, const char *filename, char **final_path, char } } +int +UpdateServerPolicy(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData data, void *cookie) +{ + SSLConfigParams *params = SSLConfig::acquire(); + char *verify_server = data.rec_string; + if (params != nullptr && verify_server != nullptr) { + Debug("ssl_load", "New Server Policy %s", verify_server); + params->SetServerPolicy(verify_server); + } else { + Debug("ssl_load", "Failed to load new Server Policy %p %p", verify_server, params); + } + return 0; +} + +int +UpdateServerPolicyProperties(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData data, void *cookie) +{ + SSLConfigParams *params = SSLConfig::acquire(); + char *verify_server = data.rec_string; + if (params != nullptr && verify_server != nullptr) { + params->SetServerPolicyProperties(verify_server); + } + return 0; +} + +void +SSLConfigParams::SetServerPolicyProperties(const char *verify_server) +{ + if (strcmp(verify_server, "SIGNATURE") == 0) { + verifyServerProperties = YamlSNIConfig::Property::SIGNATURE_MASK; + } else if (strcmp(verify_server, "NAME") == 0) { + verifyServerProperties = YamlSNIConfig::Property::NAME_MASK; + } else if (strcmp(verify_server, "ALL") == 0) { + verifyServerProperties = YamlSNIConfig::Property::ALL_MASK; + } else if (strcmp(verify_server, "NONE") == 0) { + verifyServerProperties = YamlSNIConfig::Property::NONE; + } else { + Warning("%s is invalid for proxy.config.ssl.client.verify.server.properties. Should be one of ALL, SIGNATURE, NAME, or NONE. " + "Default is ALL", + verify_server); + verifyServerProperties = YamlSNIConfig::Property::NONE; + } +} + +void +SSLConfigParams::SetServerPolicy(const char *verify_server) +{ + if (strcmp(verify_server, "DISABLED") == 0) { + verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; + } else if (strcmp(verify_server, "PERMISSIVE") == 0) { + verifyServerPolicy = YamlSNIConfig::Policy::PERMISSIVE; + } else if (strcmp(verify_server, "ENFORCED") == 0) { + verifyServerPolicy = YamlSNIConfig::Policy::ENFORCED; + } else { + Warning("%s is invalid for proxy.config.ssl.client.verify.server.policy. Should be one of DISABLED, PERMISSIVE, or ENFORCED. " + "Default is DISABLED", + verify_server); + verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; + } +} + void SSLConfigParams::initialize() { @@ -389,34 +450,14 @@ SSLConfigParams::initialize() char *verify_server = nullptr; REC_ReadConfigStringAlloc(verify_server, "proxy.config.ssl.client.verify.server.policy"); - if (strcmp(verify_server, "DISABLED") == 0) { - verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; - } else if (strcmp(verify_server, "PERMISSIVE") == 0) { - verifyServerPolicy = YamlSNIConfig::Policy::PERMISSIVE; - } else if (strcmp(verify_server, "ENFORCED") == 0) { - verifyServerPolicy = YamlSNIConfig::Policy::ENFORCED; - } else { - Warning("%s is invalid for proxy.config.ssl.client.verify.server.policy. Should be one of DISABLED, PERMISSIVE, or ENFORCED", - verify_server); - verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; - } + this->SetServerPolicy(verify_server); ats_free(verify_server); + REC_RegisterConfigUpdateFunc("proxy.config.ssl.client.verify.server.policy", UpdateServerPolicy, nullptr); REC_ReadConfigStringAlloc(verify_server, "proxy.config.ssl.client.verify.server.properties"); - if (strcmp(verify_server, "SIGNATURE") == 0) { - verifyServerProperties = YamlSNIConfig::Property::SIGNATURE_MASK; - } else if (strcmp(verify_server, "NAME") == 0) { - verifyServerProperties = YamlSNIConfig::Property::NAME_MASK; - } else if (strcmp(verify_server, "ALL") == 0) { - verifyServerProperties = YamlSNIConfig::Property::ALL_MASK; - } else if (strcmp(verify_server, "NONE") == 0) { - verifyServerProperties = YamlSNIConfig::Property::NONE; - } else { - Warning("%s is invalid for proxy.config.ssl.client.verify.server.properties. Should be one of SIGNATURE, NAME, or ALL", - verify_server); - verifyServerProperties = YamlSNIConfig::Property::NONE; - } + this->SetServerPolicyProperties(verify_server); ats_free(verify_server); + REC_RegisterConfigUpdateFunc("proxy.config.ssl.client.verify.server.properties", UpdateServerPolicyProperties, nullptr); ssl_client_cert_filename = nullptr; ssl_client_cert_path = nullptr; diff --git a/tests/gold_tests/tls/tls_verify4.test.py b/tests/gold_tests/tls/tls_verify4.test.py new file mode 100644 index 00000000000..6eea3f50b48 --- /dev/null +++ b/tests/gold_tests/tls/tls_verify4.test.py @@ -0,0 +1,190 @@ +''' +''' +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Test.Summary = ''' +Test tls server certificate verification options +Specifically update via traffic_ctl (reloadable) +''' + +# Define default ATS +ts = Test.MakeATSProcess("ts", select_ports=True, enable_tls=True) +server_foo = Test.MakeOriginServer("server_foo", + ssl=True, + options={"--key": "{0}/signed-foo.key".format(Test.RunDirectory), + "--cert": "{0}/signed-foo.pem".format(Test.RunDirectory)}) +server_bar = Test.MakeOriginServer("server_bar", + ssl=True, + options={"--key": "{0}/signed-bar.key".format(Test.RunDirectory), + "--cert": "{0}/signed-bar.pem".format(Test.RunDirectory)}) +server = Test.MakeOriginServer("server", ssl=True) + +request_foo_header = {"headers": "GET / HTTP/1.1\r\nHost: foo.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +request_bad_foo_header = {"headers": "GET / HTTP/1.1\r\nHost: bad_foo.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +request_bar_header = {"headers": "GET / HTTP/1.1\r\nHost: bar.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +request_bad_bar_header = {"headers": "GET / HTTP/1.1\r\nHost: bad_bar.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +server_foo.addResponse("sessionlog.json", request_foo_header, response_header) +server_foo.addResponse("sessionlog.json", request_bad_foo_header, response_header) +server_bar.addResponse("sessionlog.json", request_bar_header, response_header) +server_bar.addResponse("sessionlog.json", request_bad_bar_header, response_header) + +# add ssl materials like key, certificates for the server +ts.addSSLfile("ssl/signed-foo.pem") +ts.addSSLfile("ssl/signed-foo.key") +ts.addSSLfile("ssl/signed-bar.pem") +ts.addSSLfile("ssl/signed-bar.key") +ts.addSSLfile("ssl/server.pem") +ts.addSSLfile("ssl/server.key") +ts.addSSLfile("ssl/signer.pem") +ts.addSSLfile("ssl/signer.key") + +ts.Disk.remap_config.AddLine( + 'map https://foo.com/ https://127.0.0.1:{0}'.format(server_foo.Variables.SSL_Port)) +ts.Disk.remap_config.AddLine( + 'map https://bad_foo.com/ https://127.0.0.1:{0}'.format(server_foo.Variables.SSL_Port)) +ts.Disk.remap_config.AddLine( + 'map https://bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port)) +ts.Disk.remap_config.AddLine( + 'map https://bad_bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port)) +ts.Disk.remap_config.AddLine( + 'map / https://127.0.0.1:{0}'.format(server.Variables.SSL_Port)) + +ts.Disk.ssl_multicert_config.AddLine( + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key' +) + +# Case 1, global config policy=permissive properties=signature +# override for foo.com policy=enforced properties=all +ts.Disk.records_config.update({ + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2', + # set global policy + 'proxy.config.ssl.client.verify.server.policy': 'ENFORCED', + 'proxy.config.ssl.client.verify.server.properties': 'ALL', + 'proxy.config.ssl.client.CA.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.client.CA.cert.filename': 'signer.pem', + 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.url_remap.pristine_host_hdr': 1, + 'proxy.config.diags.debug.enabled': 0, + 'proxy.config.diags.debug.tags': 'ssl' +}) + +tr = Test.AddTestRun("default-enforce-bad-sig") +tr.Setup.Copy("ssl/signed-foo.key") +tr.Setup.Copy("ssl/signed-foo.pem") +tr.Setup.Copy("ssl/signed-bar.key") +tr.Setup.Copy("ssl/signed-bar.pem") +tr.Processes.Default.Command = "curl -k -H \"host: random2.com\" https://127.0.0.1:{0}".format(ts.Variables.ssl_port) +tr.ReturnCode = 0 +tr.Processes.Default.StartBefore(server_foo) +tr.Processes.Default.StartBefore(server_bar) +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(Test.Processes.ts) +tr.StillRunningAfter = server +tr.StillRunningAfter = ts +tr.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Curl attempt should have failed") + +tr2 = Test.AddTestRun("Update config files") +recordspath = ts.Disk.records_config.AbsPath +# recreate the records.config with the cert filename changed +tr2.Disk.File(recordspath, id="records_config", typename="ats:config:records"), +tr2.Disk.records_config.update({ + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2', + # set global policy + 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', + 'proxy.config.ssl.client.verify.server.properties': 'ALL', + 'proxy.config.ssl.client.CA.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.client.CA.cert.filename': 'signer.pem', + 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.url_remap.pristine_host_hdr': 1, + 'proxy.config.diags.debug.enabled': 0, + 'proxy.config.diags.debug.tags': 'ssl' +}) +tr2.StillRunningAfter = ts +tr2.StillRunningAfter = server +tr2.Processes.Default.Command = 'echo Updated configs' +# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket +tr2.Processes.Default.Env = ts.Env +tr2.Processes.Default.ReturnCode = 0 + +# Change the config to PERMISSIVE. Same command should work now +trreload = Test.AddTestRun("Reload config") +trreload.StillRunningAfter = ts +trreload.StillRunningAfter = server +# takes a few seconds for the reload to be ready for the next connection +trreload.Processes.Default.Command = 'traffic_ctl config reload; sleep 5' +# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket +trreload.Processes.Default.Env = ts.Env +trreload.Processes.Default.ReturnCode = 0 + +tragain = Test.AddTestRun("permissive-after-update") +tragain.Processes.Default.Command = "curl -k -H \"host: random3.com\" https://127.0.0.1:{0}".format(ts.Variables.ssl_port) +tragain.ReturnCode = 0 +tragain.StillRunningAfter = server +tragain.StillRunningAfter = ts +tragain.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Curl attempt should have succeeded") + +tr2 = Test.AddTestRun("Update config files to enforced") +recordspath = ts.Disk.records_config.AbsPath +# recreate the records.config with the cert filename changed +tr2.Disk.File(recordspath, id="records_config", typename="ats:config:records"), +tr2.Disk.records_config.update({ + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2', + # set global policy + 'proxy.config.ssl.client.verify.server.policy': 'ENFORCED', + 'proxy.config.ssl.client.verify.server.properties': 'ALL', + 'proxy.config.ssl.client.CA.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.client.CA.cert.filename': 'signer.pem', + 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.url_remap.pristine_host_hdr': 1, + 'proxy.config.diags.debug.enabled': 0, + 'proxy.config.diags.debug.tags': 'ssl' +}) +tr2.StillRunningAfter = ts +tr2.StillRunningAfter = server +tr2.Processes.Default.Command = 'echo Updated configs to ENFORCED' +# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket +tr2.Processes.Default.Env = ts.Env +tr2.Processes.Default.ReturnCode = 0 + +trreload = Test.AddTestRun("Reload config again") +trreload.StillRunningAfter = ts +trreload.StillRunningAfter = server +# takes a few seconds for the reload to be ready for the next connection +trreload.Processes.Default.Command = 'traffic_ctl config reload; sleep 5' +# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket +trreload.Processes.Default.Env = ts.Env +trreload.Processes.Default.ReturnCode = 0 + +tragain = Test.AddTestRun("enforced-after-update") +tragain.Processes.Default.Command = "curl -k -H \"host: random4.com\" https://127.0.0.1:{0}".format(ts.Variables.ssl_port) +tragain.ReturnCode = 0 +tragain.StillRunningAfter = server +tragain.StillRunningAfter = ts +tragain.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Curl attempt should have succeeded") + +# No name checking for the sig-only permissive override for bad_bar +ts.Disk.diags_log.Content += Testers.ContainsExpression( + "Core server certificate verification failed for \(random3.com\). Action=Continue", "Permissive can connect") +ts.Disk.diags_log.Content += Testers.ContainsExpression( + "Core server certificate verification failed for \(random2.com\). Action=Terminate", "Enforced cannot connect")