github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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  	"net/http"
     9  	"net/url"
    10  
    11  	"github.com/juju/loggo"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/worker/v3/workertest"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	coretesting "github.com/juju/juju/testing"
    17  	"github.com/juju/juju/worker/httpserver"
    18  )
    19  
    20  type certSuite struct {
    21  	workerFixture
    22  }
    23  
    24  var _ = gc.Suite(&certSuite{})
    25  
    26  func testSNIGetter(cert *tls.Certificate) httpserver.SNIGetterFunc {
    27  	return httpserver.SNIGetterFunc(func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
    28  		return cert, nil
    29  	})
    30  }
    31  
    32  func (s *certSuite) SetUpTest(c *gc.C) {
    33  	s.workerFixture.SetUpTest(c)
    34  	tlsConfig := httpserver.InternalNewTLSConfig(
    35  		"",
    36  		"https://0.1.2.3/no-autocert-here",
    37  		nil,
    38  		testSNIGetter(coretesting.ServerTLSCert),
    39  		loggo.GetLogger("test"),
    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  }
    47  
    48  func (s *certSuite) handler(w http.ResponseWriter, req *http.Request) {
    49  	w.WriteHeader(http.StatusOK)
    50  	w.Write([]byte("yay"))
    51  }
    52  
    53  func (s *certSuite) TestAutocertFailure(c *gc.C) {
    54  	// We don't have a fake autocert server, but we can at least
    55  	// smoke test that the autocert path is followed when we try
    56  	// to connect to a DNS name - the AutocertURL configured
    57  	// by the testing suite is invalid so it should fail.
    58  
    59  	// Dropping the handler returned here disables the challenge
    60  	// listener.
    61  	tlsConfig := httpserver.InternalNewTLSConfig(
    62  		"somewhere.example",
    63  		"https://0.1.2.3/no-autocert-here",
    64  		nil,
    65  		testSNIGetter(coretesting.ServerTLSCert),
    66  		loggo.GetLogger("test"),
    67  	)
    68  	s.config.TLSConfig = tlsConfig
    69  
    70  	worker, err := httpserver.NewWorker(s.config)
    71  	c.Assert(err, jc.ErrorIsNil)
    72  	defer workertest.CleanKill(c, worker)
    73  
    74  	parsed, err := url.Parse(worker.URL())
    75  	c.Assert(err, jc.ErrorIsNil)
    76  
    77  	entries := gatherLog(func() {
    78  		_, err := tls.Dial("tcp", parsed.Host, &tls.Config{
    79  			ServerName: "somewhere.example",
    80  		})
    81  		expectedErr := `.*x509: certificate is valid for .*, not somewhere.example`
    82  		// We can't get an autocert certificate, so we'll fall back to the local certificate
    83  		// which isn't valid for connecting to somewhere.example.
    84  		c.Assert(err, gc.ErrorMatches, expectedErr)
    85  	})
    86  	// We will log the failure to get the certificate, thus assuring us that we actually tried.
    87  	c.Assert(entries, jc.LogMatches, jc.SimpleMessages{{
    88  		loggo.INFO,
    89  		`getting certificate for server name "somewhere.example"`,
    90  	}, {
    91  		loggo.ERROR,
    92  		`.*cannot get autocert certificate for "somewhere.example": Get ["]?https://0\.1\.2\.3/no-autocert-here["]?: .*`,
    93  	}})
    94  }
    95  
    96  func (s *certSuite) TestAutocertNameMismatch(c *gc.C) {
    97  	tlsConfig := httpserver.InternalNewTLSConfig(
    98  		"somewhere.example",
    99  		"https://0.1.2.3/no-autocert-here",
   100  		nil,
   101  		testSNIGetter(coretesting.ServerTLSCert),
   102  		loggo.GetLogger("test"),
   103  	)
   104  	s.config.TLSConfig = tlsConfig
   105  
   106  	worker, err := httpserver.NewWorker(s.config)
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	defer workertest.CleanKill(c, worker)
   109  
   110  	parsed, err := url.Parse(worker.URL())
   111  	c.Assert(err, jc.ErrorIsNil)
   112  
   113  	entries := gatherLog(func() {
   114  		_, err := tls.Dial("tcp", parsed.Host, &tls.Config{
   115  			ServerName: "somewhere.else",
   116  		})
   117  		expectedErr := `.*x509: certificate is valid for .*, not somewhere.else`
   118  		// We can't get an autocert certificate, so we'll fall back to the local certificate
   119  		// which isn't valid for connecting to somewhere.example.
   120  		c.Assert(err, gc.ErrorMatches, expectedErr)
   121  	})
   122  	// Check that we logged the mismatch.
   123  	c.Assert(entries, jc.LogMatches, jc.SimpleMessages{{
   124  		loggo.ERROR,
   125  		`.*cannot get autocert certificate for "somewhere.else": acme/autocert: host "somewhere.else" not configured in HostWhitelist`,
   126  	}})
   127  }
   128  
   129  func (s *certSuite) TestAutocertNoAutocertDNSName(c *gc.C) {
   130  	worker, err := httpserver.NewWorker(s.config)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	defer workertest.CleanKill(c, worker)
   133  
   134  	parsed, err := url.Parse(worker.URL())
   135  	c.Assert(err, jc.ErrorIsNil)
   136  
   137  	entries := gatherLog(func() {
   138  		_, err := tls.Dial("tcp", parsed.Host, &tls.Config{
   139  			ServerName: "somewhere.example",
   140  		})
   141  		expectedErr := `.*x509: certificate is valid for .*, not somewhere.example`
   142  		// We can't get an autocert certificate, so we'll fall back to the local certificate
   143  		// which isn't valid for connecting to somewhere.example.
   144  		c.Assert(err, gc.ErrorMatches, expectedErr)
   145  	})
   146  	// Check that we never logged a failure to get the certificate.
   147  	c.Assert(entries, gc.Not(jc.LogMatches), jc.SimpleMessages{{
   148  		loggo.ERROR,
   149  		`.*cannot get autocert certificate.*`,
   150  	}})
   151  }
   152  
   153  func gatherLog(f func()) []loggo.Entry {
   154  	var tw loggo.TestWriter
   155  	err := loggo.RegisterWriter("test", &tw)
   156  	if err != nil {
   157  		panic(err)
   158  	}
   159  	defer loggo.RemoveWriter("test")
   160  	f()
   161  	return tw.Log()
   162  }