github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/httpserver/cert_test.go (about) 1 // Copyright 2016-2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package httpserver_test 5 6 import ( 7 "crypto/tls" 8 "crypto/x509" 9 "io/ioutil" 10 "net/http" 11 "net/url" 12 "runtime" 13 "time" 14 15 "github.com/juju/loggo" 16 jc "github.com/juju/testing/checkers" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/juju/worker.v1/workertest" 19 20 "github.com/juju/juju/cert" 21 coretesting "github.com/juju/juju/testing" 22 "github.com/juju/juju/worker/httpserver" 23 ) 24 25 type certSuite struct { 26 workerFixture 27 28 cert *tls.Certificate 29 } 30 31 var _ = gc.Suite(&certSuite{}) 32 33 func (s *certSuite) SetUpTest(c *gc.C) { 34 s.workerFixture.SetUpTest(c) 35 tlsConfig := httpserver.InternalNewTLSConfig( 36 "", 37 "https://0.1.2.3/no-autocert-here", 38 nil, 39 func() *tls.Certificate { return s.cert }, 40 ) 41 // Copy the root CAs across. 42 tlsConfig.RootCAs = s.config.TLSConfig.RootCAs 43 s.config.TLSConfig = tlsConfig 44 s.config.TLSConfig.ServerName = "juju-apiserver" 45 s.config.Mux.AddHandler("GET", "/hey", http.HandlerFunc(s.handler)) 46 s.cert = coretesting.ServerTLSCert 47 } 48 49 func (s *certSuite) handler(w http.ResponseWriter, req *http.Request) { 50 w.WriteHeader(http.StatusOK) 51 w.Write([]byte("yay")) 52 } 53 54 func (s *certSuite) request(url string) (*http.Response, error) { 55 // Create the client each time to ensure that we get the 56 // certificate again. 57 client := &http.Client{ 58 Transport: &http.Transport{ 59 TLSClientConfig: s.config.TLSConfig, 60 }, 61 } 62 return client.Get(url) 63 } 64 65 func (s *certSuite) TestUpdateCert(c *gc.C) { 66 worker, err := httpserver.NewWorker(s.config) 67 c.Assert(err, jc.ErrorIsNil) 68 defer workertest.CleanKill(c, worker) 69 70 url := worker.URL() + "/hey" 71 // Sanity check that the server works initially. 72 resp, err := s.request(url) 73 c.Assert(err, jc.ErrorIsNil) 74 defer resp.Body.Close() 75 c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) 76 content, err := ioutil.ReadAll(resp.Body) 77 c.Assert(err, jc.ErrorIsNil) 78 c.Assert(string(content), gc.Equals, "yay") 79 80 // Create a new certificate that's a year out of date, so we can 81 // tell that the server is using it because the connection will fail. 82 srvCert, srvKey, err := cert.NewServer(coretesting.CACert, coretesting.CAKey, time.Now().AddDate(-1, 0, 0), nil) 83 c.Assert(err, jc.ErrorIsNil) 84 badTLSCert, err := tls.X509KeyPair([]byte(srvCert), []byte(srvKey)) 85 if err != nil { 86 panic(err) 87 } 88 x509Cert, err := x509.ParseCertificate(badTLSCert.Certificate[0]) 89 if err != nil { 90 panic(err) 91 } 92 badTLSCert.Leaf = x509Cert 93 94 // Check that we can't connect to the server because of the bad certificate. 95 s.cert = &badTLSCert 96 _, err = s.request(url) 97 c.Assert(err, gc.ErrorMatches, `.*: certificate has expired or is not yet valid`) 98 99 // Replace the working certificate and check that we can connect again. 100 s.cert = coretesting.ServerTLSCert 101 resp, err = s.request(url) 102 c.Assert(err, jc.ErrorIsNil) 103 resp.Body.Close() 104 } 105 106 func (s *certSuite) TestAutocertFailure(c *gc.C) { 107 // We don't have a fake autocert server, but we can at least 108 // smoke test that the autocert path is followed when we try 109 // to connect to a DNS name - the AutocertURL configured 110 // by the testing suite is invalid so it should fail. 111 112 // Dropping the handler returned here disables the challenge 113 // listener. 114 tlsConfig := httpserver.InternalNewTLSConfig( 115 "somewhere.example", 116 "https://0.1.2.3/no-autocert-here", 117 nil, 118 func() *tls.Certificate { return s.cert }, 119 ) 120 s.config.TLSConfig = tlsConfig 121 122 worker, err := httpserver.NewWorker(s.config) 123 c.Assert(err, jc.ErrorIsNil) 124 defer workertest.CleanKill(c, worker) 125 126 parsed, err := url.Parse(worker.URL()) 127 c.Assert(err, jc.ErrorIsNil) 128 129 entries := gatherLog(func() { 130 _, err := tls.Dial("tcp", parsed.Host, &tls.Config{ 131 ServerName: "somewhere.example", 132 }) 133 expectedErr := `x509: certificate is valid for \*, not somewhere.example` 134 if runtime.GOOS == "windows" { 135 // For some reason, windows doesn't think that the certificate is signed 136 // by a valid authority. This could be problematic. 137 expectedErr = "x509: certificate signed by unknown authority" 138 } 139 // We can't get an autocert certificate, so we'll fall back to the local certificate 140 // which isn't valid for connecting to somewhere.example. 141 c.Assert(err, gc.ErrorMatches, expectedErr) 142 }) 143 // We will log the failure to get the certificate, thus assuring us that we actually tried. 144 c.Assert(entries, jc.LogMatches, jc.SimpleMessages{{ 145 loggo.ERROR, 146 `.*cannot get autocert certificate for "somewhere.example": Get https://0\.1\.2\.3/no-autocert-here: .*`, 147 }}) 148 } 149 150 func (s *certSuite) TestAutocertNameMismatch(c *gc.C) { 151 tlsConfig := httpserver.InternalNewTLSConfig( 152 "somewhere.example", 153 "https://0.1.2.3/no-autocert-here", 154 nil, 155 func() *tls.Certificate { return s.cert }, 156 ) 157 s.config.TLSConfig = tlsConfig 158 159 worker, err := httpserver.NewWorker(s.config) 160 c.Assert(err, jc.ErrorIsNil) 161 defer workertest.CleanKill(c, worker) 162 163 parsed, err := url.Parse(worker.URL()) 164 c.Assert(err, jc.ErrorIsNil) 165 166 entries := gatherLog(func() { 167 _, err := tls.Dial("tcp", parsed.Host, &tls.Config{ 168 ServerName: "somewhere.else", 169 }) 170 expectedErr := `x509: certificate is valid for \*, not somewhere.else` 171 if runtime.GOOS == "windows" { 172 // For some reason, windows doesn't think that the certificate is signed 173 // by a valid authority. This could be problematic. 174 expectedErr = "x509: certificate signed by unknown authority" 175 } 176 // We can't get an autocert certificate, so we'll fall back to the local certificate 177 // which isn't valid for connecting to somewhere.example. 178 c.Assert(err, gc.ErrorMatches, expectedErr) 179 }) 180 // Check that we logged the mismatch. 181 c.Assert(entries, jc.LogMatches, jc.SimpleMessages{{ 182 loggo.ERROR, 183 `.*cannot get autocert certificate for "somewhere.else": acme/autocert: host not configured`, 184 }}) 185 } 186 187 func (s *certSuite) TestAutocertNoAutocertDNSName(c *gc.C) { 188 worker, err := httpserver.NewWorker(s.config) 189 c.Assert(err, jc.ErrorIsNil) 190 defer workertest.CleanKill(c, worker) 191 192 parsed, err := url.Parse(worker.URL()) 193 c.Assert(err, jc.ErrorIsNil) 194 195 entries := gatherLog(func() { 196 _, err := tls.Dial("tcp", parsed.Host, &tls.Config{ 197 ServerName: "somewhere.example", 198 }) 199 expectedErr := `x509: certificate is valid for \*, not somewhere.example` 200 if runtime.GOOS == "windows" { 201 // For some reason, windows doesn't think that the certificate is signed 202 // by a valid authority. This could be problematic. 203 expectedErr = "x509: certificate signed by unknown authority" 204 } 205 // We can't get an autocert certificate, so we'll fall back to the local certificate 206 // which isn't valid for connecting to somewhere.example. 207 c.Assert(err, gc.ErrorMatches, expectedErr) 208 }) 209 // Check that we never logged a failure to get the certificate. 210 c.Assert(entries, gc.Not(jc.LogMatches), jc.SimpleMessages{{ 211 loggo.ERROR, 212 `.*cannot get autocert certificate.*`, 213 }}) 214 } 215 216 func gatherLog(f func()) []loggo.Entry { 217 var tw loggo.TestWriter 218 err := loggo.RegisterWriter("test", &tw) 219 if err != nil { 220 panic(err) 221 } 222 defer loggo.RemoveWriter("test") 223 f() 224 return tw.Log() 225 }