From 18f9dcb5232f01bdd91a68f52ad327a05f1478ee Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Wed, 6 Sep 2023 12:27:40 +0200 Subject: [PATCH] feat(webconnectivityqa): import misconfigured-TLS test cases (#1243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Checklist - [x] I have read the [contribution guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md) - [x] reference issue for this pull request: https://github.com/ooni/probe/issues/1803 - [x] if you changed anything related to how experiments work and you need to reflect these changes in the ooni/spec repository, please link to the related ooni/spec pull request: N/A - [x] if you changed code inside an experiment, make sure you bump its version number: N/A ## Description This diff imports the misconfigured-TLS test cases of the QA/webconnectivity.py test suite in webconnectivityqa. The only QA/webconnectivity.py test case we're not merging is the one about self-signed certificate, which are equivalent enough to an unknown root certificate that it seems unimportant to merge them. In other word, we have basically finished rewriting Jafar. Now it will be time to drop Jafar. 😅 --- QA/webconnectivity.py | 152 ------------------ go.mod | 2 +- go.sum | 4 +- .../experiment/webconnectivityqa/badssl.go | 101 ++++++++++++ .../webconnectivityqa/badssl_test.go | 51 ++++++ .../experiment/webconnectivityqa/testcase.go | 5 + internal/netemx/address.go | 6 + internal/netemx/badssl.go | 125 ++++++++++++++ internal/netemx/badssl_test.go | 63 ++++++++ internal/netemx/scenario.go | 24 ++- 10 files changed, 375 insertions(+), 158 deletions(-) create mode 100644 internal/experiment/webconnectivityqa/badssl.go create mode 100644 internal/experiment/webconnectivityqa/badssl_test.go create mode 100644 internal/netemx/badssl.go create mode 100644 internal/netemx/badssl_test.go diff --git a/QA/webconnectivity.py b/QA/webconnectivity.py index bd235afdaa..3c8555cc07 100755 --- a/QA/webconnectivity.py +++ b/QA/webconnectivity.py @@ -47,80 +47,6 @@ def assert_status_flags_are(ooni_exe, tk, desired): assert tk["x_status"] == desired -def webconnectivity_https_expired_certificate(ooni_exe, outfile): - """Test case where the domain's certificate is expired""" - args = [] - tk = execute_jafar_and_return_validated_test_keys( - ooni_exe, - outfile, - "-i https://expired.badssl.com/ web_connectivity", - "webconnectivity_https_expired_certificate", - args, - ) - assert tk["dns_experiment_failure"] == None - assert tk["dns_consistency"] == "consistent" - assert tk["control_failure"] == None - if "miniooni" in ooni_exe: - assert tk["http_experiment_failure"] == "ssl_invalid_certificate" - else: - assert "certificate verify failed" in tk["http_experiment_failure"] - assert tk["body_length_match"] == None - assert tk["body_proportion"] == 0 - assert tk["status_code_match"] == None - assert tk["headers_match"] == None - assert tk["title_match"] == None - # The following strikes me as a measurement_kit bug. We are saying - # that all is good with a domain where actually we don't know why the - # control is failed and that is clearly not accessible according to - # our measurement of the domain (certificate expired). - # - # See . - if "miniooni" in ooni_exe: - assert tk["blocking"] == None - assert tk["accessible"] == None - else: - assert tk["blocking"] == False - assert tk["accessible"] == True - assert_status_flags_are(ooni_exe, tk, 16) - - -def webconnectivity_https_wrong_host(ooni_exe, outfile): - """Test case where the hostname is wrong for the certificate""" - args = [] - tk = execute_jafar_and_return_validated_test_keys( - ooni_exe, - outfile, - "-i https://wrong.host.badssl.com/ web_connectivity", - "webconnectivity_https_wrong_host", - args, - ) - assert tk["dns_experiment_failure"] == None - assert tk["dns_consistency"] == "consistent" - assert tk["control_failure"] == None - if "miniooni" in ooni_exe: - assert tk["http_experiment_failure"] == "ssl_invalid_hostname" - else: - assert "certificate verify failed" in tk["http_experiment_failure"] - assert tk["body_length_match"] == None - assert tk["body_proportion"] == 0 - assert tk["status_code_match"] == None - assert tk["headers_match"] == None - assert tk["title_match"] == None - # The following strikes me as a measurement_kit bug. We are saying - # that all is good with a domain where actually we don't know why the - # control is failed and that is clearly not accessible according to - # our measurement of the domain (wrong host for certificate). - # - # See . - if "miniooni" in ooni_exe: - assert tk["blocking"] == None - assert tk["accessible"] == None - else: - assert tk["blocking"] == False - assert tk["accessible"] == True - assert_status_flags_are(ooni_exe, tk, 16) - - def webconnectivity_https_self_signed(ooni_exe, outfile): """Test case where the certificate is self signed""" args = [] @@ -158,91 +84,13 @@ def webconnectivity_https_self_signed(ooni_exe, outfile): assert_status_flags_are(ooni_exe, tk, 16) -def webconnectivity_https_untrusted_root(ooni_exe, outfile): - """Test case where the certificate has an untrusted root""" - args = [] - tk = execute_jafar_and_return_validated_test_keys( - ooni_exe, - outfile, - "-i https://untrusted-root.badssl.com/ web_connectivity", - "webconnectivity_https_untrusted_root", - args, - ) - assert tk["dns_experiment_failure"] == None - assert tk["dns_consistency"] == "consistent" - assert tk["control_failure"] == None - if "miniooni" in ooni_exe: - assert tk["http_experiment_failure"] == "ssl_unknown_authority" - else: - assert "certificate verify failed" in tk["http_experiment_failure"] - assert tk["body_length_match"] == None - assert tk["body_proportion"] == 0 - assert tk["status_code_match"] == None - assert tk["headers_match"] == None - assert tk["title_match"] == None - # The following strikes me as a measurement_kit bug. We are saying - # that all is good with a domain where actually we don't know why the - # control is failed and that is clearly not accessible according to - # our measurement of the domain (untrusted root certificate). - # - # See . - if "miniooni" in ooni_exe: - assert tk["blocking"] == None - assert tk["accessible"] == None - else: - assert tk["blocking"] == False - assert tk["accessible"] == True - assert_status_flags_are(ooni_exe, tk, 16) - - -def webconnectivity_https_unknown_authority_with_inconsistent_dns(ooni_exe, outfile): - """Test case where the DNS is sending us towards a website where - we're served an invalid certificate""" - args = [ - "-iptables-hijack-dns-to", - "127.0.0.1:53", - "-dns-proxy-hijack", - "example.org", - "-bad-proxy-address-tls", - "127.0.0.1:443", - "-tls-proxy-address", - "127.0.0.1:4114", - ] - tk = execute_jafar_and_return_validated_test_keys( - ooni_exe, - outfile, - "-i https://example.org/ web_connectivity", - "webconnectivity_https_unknown_authority_with_inconsistent_dns", - args, - ) - assert tk["dns_experiment_failure"] == None - assert tk["dns_consistency"] == "inconsistent" - assert tk["control_failure"] == None - if "miniooni" in ooni_exe: - assert tk["http_experiment_failure"] == "ssl_unknown_authority" - else: - assert "certificate verify failed" in tk["http_experiment_failure"] - assert tk["body_length_match"] == None - assert tk["body_proportion"] == 0 - assert tk["status_code_match"] == None - assert tk["headers_match"] == None - assert tk["title_match"] == None - assert tk["blocking"] == "dns" - assert tk["accessible"] == False - assert_status_flags_are(ooni_exe, tk, 9248) - - def main(): if len(sys.argv) != 2: sys.exit("usage: %s /path/to/ooniprobelegacy-like/binary" % sys.argv[0]) outfile = "webconnectivity.jsonl" ooni_exe = sys.argv[1] tests = [ - webconnectivity_https_expired_certificate, - webconnectivity_https_wrong_host, webconnectivity_https_self_signed, - webconnectivity_https_untrusted_root, - webconnectivity_https_unknown_authority_with_inconsistent_dns, ] for test in tests: test(ooni_exe, outfile) diff --git a/go.mod b/go.mod index a8ed9e329e..2f2f5cd205 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 github.com/montanaflynn/stats v0.7.1 github.com/ooni/go-libtor v1.1.8 - github.com/ooni/netem v0.0.0-20230905233956-4c9ebf9611c6 + github.com/ooni/netem v0.0.0-20230906091637-85d962536ff3 github.com/ooni/oocrypto v0.5.3 github.com/ooni/oohttp v0.6.3 github.com/ooni/probe-assets v0.18.0 diff --git a/go.sum b/go.sum index 991c07a68e..327cbd7c56 100644 --- a/go.sum +++ b/go.sum @@ -483,8 +483,8 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= -github.com/ooni/netem v0.0.0-20230905233956-4c9ebf9611c6 h1:0GHOnir3Dy4BIsoYTKPxPa3ixNOBxx1VSWsV9qxCfN8= -github.com/ooni/netem v0.0.0-20230905233956-4c9ebf9611c6/go.mod h1:3LJOzTIu2O4ADDJN2ILG4ViJOqyH/u9fKY8QT2Rma8Y= +github.com/ooni/netem v0.0.0-20230906091637-85d962536ff3 h1:zpTbzNzpo00cKbjLLnWMKjZeGLdoNC81vMiBDiur7NU= +github.com/ooni/netem v0.0.0-20230906091637-85d962536ff3/go.mod h1:3LJOzTIu2O4ADDJN2ILG4ViJOqyH/u9fKY8QT2Rma8Y= github.com/ooni/oocrypto v0.5.3 h1:CAb0Ze6q/EWD1PRGl9KqpzMfkut4O3XMaiKYsyxrWOs= github.com/ooni/oocrypto v0.5.3/go.mod h1:HjEQ5pQBl6btcWgAsKKq1tFo8CfBrZu63C/vPAUGIDk= github.com/ooni/oohttp v0.6.3 h1:MHydpeAPU/LSDSI/hIFJwZm4afBhd2Yo+rNxxFdeMCY= diff --git a/internal/experiment/webconnectivityqa/badssl.go b/internal/experiment/webconnectivityqa/badssl.go new file mode 100644 index 0000000000..573cec49ca --- /dev/null +++ b/internal/experiment/webconnectivityqa/badssl.go @@ -0,0 +1,101 @@ +package webconnectivityqa + +import ( + "github.com/ooni/netem" + "github.com/ooni/probe-cli/v3/internal/netemx" +) + +// Sometimes people we measure the websites of let their certificates expire and +// we want to be confident about correctly measuring this condition +func badSSLWithExpiredCertificate() *TestCase { + return &TestCase{ + Name: "badSSLWithExpiredCertificate", + Flags: TestCaseFlagNoLTE, // LTE flags it correctly but let's focus on v0.4 for now + Input: "https://expired.badssl.com/", + Configure: func(env *netemx.QAEnv) { + // nothing + }, + ExpectErr: false, + ExpectTestKeys: &testKeys{ + DNSConsistency: "consistent", + HTTPExperimentFailure: "ssl_invalid_certificate", + XStatus: 16, // StatusAnomalyControlFailure + XNullNullFlags: 4, // analysisFlagNullNullTLSMisconfigured + Accessible: nil, + Blocking: nil, + }, + } +} + +// Sometimes people we measure the websites of misconfigured the certificate names and +// we want to be confident about correctly measuring this condition +func badSSLWithWrongServerName() *TestCase { + return &TestCase{ + Name: "badSSLWithWrongServerName", + Flags: TestCaseFlagNoLTE, // LTE flags it correctly but let's focus on v0.4 for now + Input: "https://wrong.host.badssl.com/", + Configure: func(env *netemx.QAEnv) { + // nothing + }, + ExpectErr: false, + ExpectTestKeys: &testKeys{ + DNSConsistency: "consistent", + HTTPExperimentFailure: "ssl_invalid_hostname", + XStatus: 16, // StatusAnomalyControlFailure + XNullNullFlags: 4, // analysisFlagNullNullTLSMisconfigured + Accessible: nil, + Blocking: nil, + }, + } +} + +// Let's be sure we correctly flag a website using an unknown-to-us authority. +func badSSLWithUnknownAuthorityWithConsistentDNS() *TestCase { + return &TestCase{ + Name: "badSSLWithUnknownAuthorityWithConsistentDNS", + Flags: TestCaseFlagNoLTE, // LTE flags it correctly but let's focus on v0.4 for now + Input: "https://untrusted-root.badssl.com/", + Configure: func(env *netemx.QAEnv) { + // nothing + }, + ExpectErr: false, + ExpectTestKeys: &testKeys{ + DNSConsistency: "consistent", + HTTPExperimentFailure: "ssl_unknown_authority", + XStatus: 16, // StatusAnomalyControlFailure + XNullNullFlags: 4, // analysisFlagNullNullTLSMisconfigured + Accessible: nil, + Blocking: nil, + }, + } +} + +// This test case models when we're redirected to a blockpage website using a custom CA. +func badSSLWithUnknownAuthorityWithInconsistentDNS() *TestCase { + return &TestCase{ + Name: "badSSLWithUnknownAuthorityWithInconsistentDNS", + Flags: 0, + Input: "https://www.example.com/", + Configure: func(env *netemx.QAEnv) { + + // add DPI rule to force all the cleartext DNS queries to + // point the client to used the ISPProxyAddress + env.DPIEngine().AddRule(&netem.DPISpoofDNSResponse{ + Addresses: []string{netemx.AddressBadSSLCom}, + Logger: env.Logger(), + Domain: "www.example.com", + }) + + }, + ExpectErr: false, + ExpectTestKeys: &testKeys{ + DNSConsistency: "inconsistent", + HTTPExperimentFailure: "ssl_unknown_authority", + XStatus: 9248, // StatusExperimentHTTP | StatusAnomalyTLSHandshake | StatusAnomalyDNS + XDNSFlags: 4, // AnalysisDNSUnexpectedAddrs + XBlockingFlags: 33, // analysisFlagSuccess | analysisFlagDNSBlocking + Accessible: false, + Blocking: "dns", + }, + } +} diff --git a/internal/experiment/webconnectivityqa/badssl_test.go b/internal/experiment/webconnectivityqa/badssl_test.go new file mode 100644 index 0000000000..8dc0e27af8 --- /dev/null +++ b/internal/experiment/webconnectivityqa/badssl_test.go @@ -0,0 +1,51 @@ +package webconnectivityqa + +import ( + "net/http" + "testing" + + "github.com/apex/log" + "github.com/ooni/probe-cli/v3/internal/netemx" + "github.com/ooni/probe-cli/v3/internal/netxlite" + "github.com/ooni/probe-cli/v3/internal/runtimex" +) + +func TestBadSSLConditions(t *testing.T) { + type testCaseConfig struct { + expectedErr string + testCase *TestCase + } + + testcases := []*testCaseConfig{{ + expectedErr: "ssl_unknown_authority", + testCase: badSSLWithUnknownAuthorityWithConsistentDNS(), + }, { + expectedErr: "ssl_invalid_certificate", + testCase: badSSLWithExpiredCertificate(), + }, { + expectedErr: "ssl_invalid_hostname", + testCase: badSSLWithWrongServerName(), + }, { + expectedErr: "ssl_unknown_authority", + testCase: badSSLWithUnknownAuthorityWithInconsistentDNS(), + }} + + for _, tc := range testcases { + t.Run(tc.testCase.Name, func(t *testing.T) { + env := netemx.MustNewScenario(netemx.InternetScenario) + tc.testCase.Configure(env) + + env.Do(func() { + client := netxlite.NewHTTPClientStdlib(log.Log) + req := runtimex.Try1(http.NewRequest("GET", tc.testCase.Input, nil)) + resp, err := client.Do(req) + if err == nil || err.Error() != tc.expectedErr { + t.Fatal("unexpected err", err) + } + if resp != nil { + t.Fatal("expected nil resp") + } + }) + }) + } +} diff --git a/internal/experiment/webconnectivityqa/testcase.go b/internal/experiment/webconnectivityqa/testcase.go index ebefc22eec..37ef5919db 100644 --- a/internal/experiment/webconnectivityqa/testcase.go +++ b/internal/experiment/webconnectivityqa/testcase.go @@ -37,6 +37,11 @@ type TestCase struct { // AllTestCases returns all the defined test cases. func AllTestCases() []*TestCase { return []*TestCase{ + badSSLWithUnknownAuthorityWithConsistentDNS(), + badSSLWithExpiredCertificate(), + badSSLWithWrongServerName(), + badSSLWithUnknownAuthorityWithInconsistentDNS(), + controlFailureWithSuccessfulHTTPWebsite(), controlFailureWithSuccessfulHTTPSWebsite(), diff --git a/internal/netemx/address.go b/internal/netemx/address.go index ea3f2555d4..a80f3a08f4 100644 --- a/internal/netemx/address.go +++ b/internal/netemx/address.go @@ -52,3 +52,9 @@ const AddressPublicBlockpage = "83.224.65.41" // ISPProxyAddress is the IP address of the ISP's HTTP transparent proxy. const ISPProxyAddress = "130.192.182.17" + +// AddressBitly is the IP address of bitly.com. +const AddressBitly = "67.199.248.11" + +// AddressBadSSLCom is the IP address of badssl.com. +const AddressBadSSLCom = "104.154.89.105" diff --git a/internal/netemx/badssl.go b/internal/netemx/badssl.go new file mode 100644 index 0000000000..a0140b368f --- /dev/null +++ b/internal/netemx/badssl.go @@ -0,0 +1,125 @@ +package netemx + +import ( + "context" + "crypto/tls" + "io" + "net" + "sync" + "time" + + "github.com/ooni/netem" + "github.com/ooni/probe-cli/v3/internal/model" + "github.com/ooni/probe-cli/v3/internal/runtimex" + "github.com/ooni/probe-cli/v3/internal/testingx" +) + +// BadSSLServerFactory is a [NetStackServerFactory] that instantiates +// a [NetStackServer] used for testing common TLS issues. +type BadSSLServerFactory struct{} + +var _ NetStackServerFactory = &BadSSLServerFactory{} + +// MustNewServer implements NetStackServerFactory. +func (*BadSSLServerFactory) MustNewServer(env NetStackServerFactoryEnv, stack *netem.UNetStack) NetStackServer { + return &badSSLServer{ + closers: []io.Closer{}, + logger: env.Logger(), + mu: sync.Mutex{}, + unet: stack, + } +} + +type badSSLServer struct { + closers []io.Closer + logger model.Logger + mu sync.Mutex + unet *netem.UNetStack +} + +// Close implements NetStackServer. +func (srv *badSSLServer) Close() error { + // "this method MUST be CONCURRENCY SAFE" + defer srv.mu.Unlock() + srv.mu.Lock() + + // make sure we close all the child listeners + for _, closer := range srv.closers { + _ = closer.Close() + } + + // "this method MUST be IDEMPOTENT" + srv.closers = []io.Closer{} + + return nil +} + +// MustStart implements NetStackServer. +func (srv *badSSLServer) MustStart() { + // "this method MUST be CONCURRENCY SAFE" + defer srv.mu.Unlock() + srv.mu.Lock() + + // build the listening endpoint + ipAddr := net.ParseIP(srv.unet.IPAddress()) + runtimex.Assert(ipAddr != nil, "invalid IP address") + epnt := &net.TCPAddr{IP: ipAddr, Port: 443} + + // start the server in a background goroutine + server := testingx.MustNewTLSServerEx(epnt, srv.unet, &tlsHandlerForBadSSLServer{srv.unet}) + + // track this listener as something to close later + srv.closers = append(srv.closers, server) +} + +// tlsHandlerForBadSSLServer is a [testingx.TLSHandler] that only cares about the +// handshake and applies specific wrong configurations during it. +// +// Limitation: this handler does not care about what happens after the handshake +// and just lets the underlying [*testingx.TLSServer] close the TLS conn. +type tlsHandlerForBadSSLServer struct { + unet *netem.UNetStack +} + +// GetCertificate implements testingx.TLSHandler. +func (thx *tlsHandlerForBadSSLServer) GetCertificate( + ctx context.Context, tcpConn net.Conn, chi *tls.ClientHelloInfo) (*tls.Certificate, error) { + switch chi.ServerName { + case "wrong.host.badssl.com": + // Use the correct root CA but return a certificate for a different + // host, which should cause the SNI verification to fail. + tlsConfig := thx.unet.ServerTLSConfig() + return tlsConfig.GetCertificate(&tls.ClientHelloInfo{ + CipherSuites: chi.CipherSuites, + ServerName: "wrong-host.badssl.com", // different! + SupportedCurves: chi.SupportedCurves, + SupportedPoints: chi.SupportedPoints, + SignatureSchemes: chi.SignatureSchemes, + SupportedProtos: chi.SupportedProtos, + SupportedVersions: chi.SupportedVersions, + Conn: tcpConn, + }) + + case "untrusted-root.badssl.com": + fallthrough + default: + // Create a custom MITM config and use it to negotiate TLS. Because this would be + // a different root CA, validating certs will fail the handshake. + // + // A more advanced version of this handler could choose different behaviors + // depending on the SNI provided as part of the *tls.ClientHelloInfo. + mitm := testingx.MustNewTLSMITMProviderNetem() + tlsConfig := mitm.ServerTLSConfig() + return tlsConfig.GetCertificate(chi) + + case "expired.badssl.com": + // Create on-the-fly a certificate with the right SNI but that is clearly expired. + mitm := thx.unet.TLSMITMConfig() + return mitm.Config.NewCertWithoutCacheWithTimeNow( + chi.ServerName, + func() time.Time { + return time.Date(2017, time.July, 17, 0, 0, 0, 0, time.UTC) + }, + ) + } +} diff --git a/internal/netemx/badssl_test.go b/internal/netemx/badssl_test.go new file mode 100644 index 0000000000..1dcfdd910a --- /dev/null +++ b/internal/netemx/badssl_test.go @@ -0,0 +1,63 @@ +package netemx + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "testing" + + "github.com/apex/log" + "github.com/ooni/probe-cli/v3/internal/netxlite" +) + +func TestBadSSL(t *testing.T) { + env := MustNewScenario(InternetScenario) + defer env.Close() + + env.Do(func() { + + // testcase is a testcase supported by this function + type testcase struct { + serverName string + expectErr string + } + + testcases := []testcase{{ + serverName: "untrusted-root.badssl.com", + expectErr: netxlite.FailureSSLUnknownAuthority, + }, { + serverName: "wrong.host.badssl.com", + expectErr: netxlite.FailureSSLInvalidHostname, + }, { + serverName: "expired.badssl.com", + expectErr: netxlite.FailureSSLInvalidCertificate, + }, { + // Make sure that we can use the badssl server as something we can + // force using the DNS to cause a failure + serverName: "www.example.com", + expectErr: netxlite.FailureSSLUnknownAuthority, + }} + + for _, tc := range testcases { + t.Run(fmt.Sprintf("for %s expect %s", tc.serverName, tc.expectErr), func(t *testing.T) { + tlsConfig := &tls.Config{ServerName: tc.serverName} + + tlsDialer := netxlite.NewTLSDialerWithConfig( + netxlite.NewDialerWithoutResolver(log.Log), + netxlite.NewTLSHandshakerStdlib(log.Log), + tlsConfig, + ) + + endpoint := net.JoinHostPort(AddressBadSSLCom, "443") + conn, err := tlsDialer.DialTLSContext(context.Background(), "tcp", endpoint) + if err == nil || err.Error() != tc.expectErr { + t.Fatal("unexpected error", err) + } + if conn != nil { + t.Fatal("expected nil conn") + } + }) + } + }) +} diff --git a/internal/netemx/scenario.go b/internal/netemx/scenario.go index 4a699352cf..17d90bc9a8 100644 --- a/internal/netemx/scenario.go +++ b/internal/netemx/scenario.go @@ -33,6 +33,10 @@ const ( // ScenarioRoleURLShortener means that the host is an URL shortener. ScenarioRoleURLShortener + + // ScenarioRoleBadSSL means that the host hosts services to + // measure against common TLS issues. + ScenarioRoleBadSSL ) // ScenarioDomainAddresses describes a domain and address used in a scenario. @@ -136,10 +140,19 @@ var InternetScenario = []*ScenarioDomainAddresses{{ }, { Domains: []string{"bit.ly", "bitly.com"}, Addresses: []string{ - "67.199.248.11", + AddressBitly, }, - Role: ScenarioRoleURLShortener, - WebServerFactory: nil, + Role: ScenarioRoleURLShortener, +}, { + Domains: []string{ + "wrong.host.badssl.com", + "untrusted-root.badssl.com", + "expired.badssl.com", + }, + Addresses: []string{ + AddressBadSSLCom, + }, + Role: ScenarioRoleBadSSL, }} // MustNewScenario constructs a complete testing scenario using the domains and IP @@ -223,6 +236,11 @@ func MustNewScenario(config []*ScenarioDomainAddresses) *QAEnv { for _, addr := range sad.Addresses { opts = append(opts, QAEnvOptionHTTPServer(addr, URLShortenerFactory(DefaultURLShortenerMapping))) } + + case ScenarioRoleBadSSL: + for _, addr := range sad.Addresses { + opts = append(opts, qaEnvOptionNetStack(addr, &BadSSLServerFactory{})) + } } }