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  }