github.com/bigcommerce/nomad@v0.9.3-bc/command/agent/http_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"net/url"
    15  	"strconv"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/hashicorp/nomad/nomad/mock"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  	"github.com/hashicorp/nomad/nomad/structs/config"
    22  	"github.com/hashicorp/nomad/testutil"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/ugorji/go/codec"
    25  )
    26  
    27  // makeHTTPServer returns a test server whose logs will be written to
    28  // the passed writer. If the writer is nil, the logs are written to stderr.
    29  func makeHTTPServer(t testing.TB, cb func(c *Config)) *TestAgent {
    30  	return NewTestAgent(t, t.Name(), cb)
    31  }
    32  
    33  func BenchmarkHTTPRequests(b *testing.B) {
    34  	s := makeHTTPServer(b, func(c *Config) {
    35  		c.Client.Enabled = false
    36  	})
    37  	defer s.Shutdown()
    38  
    39  	job := mock.Job()
    40  	var allocs []*structs.Allocation
    41  	count := 1000
    42  	for i := 0; i < count; i++ {
    43  		alloc := mock.Alloc()
    44  		alloc.Job = job
    45  		alloc.JobID = job.ID
    46  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
    47  		allocs = append(allocs, alloc)
    48  	}
    49  
    50  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    51  		return allocs[:count], nil
    52  	}
    53  	b.ResetTimer()
    54  
    55  	b.RunParallel(func(pb *testing.PB) {
    56  		for pb.Next() {
    57  			resp := httptest.NewRecorder()
    58  			req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
    59  			s.Server.wrap(handler)(resp, req)
    60  		}
    61  	})
    62  }
    63  
    64  func TestSetIndex(t *testing.T) {
    65  	t.Parallel()
    66  	resp := httptest.NewRecorder()
    67  	setIndex(resp, 1000)
    68  	header := resp.Header().Get("X-Nomad-Index")
    69  	if header != "1000" {
    70  		t.Fatalf("Bad: %v", header)
    71  	}
    72  	setIndex(resp, 2000)
    73  	if v := resp.Header()["X-Nomad-Index"]; len(v) != 1 {
    74  		t.Fatalf("bad: %#v", v)
    75  	}
    76  }
    77  
    78  func TestSetKnownLeader(t *testing.T) {
    79  	t.Parallel()
    80  	resp := httptest.NewRecorder()
    81  	setKnownLeader(resp, true)
    82  	header := resp.Header().Get("X-Nomad-KnownLeader")
    83  	if header != "true" {
    84  		t.Fatalf("Bad: %v", header)
    85  	}
    86  	resp = httptest.NewRecorder()
    87  	setKnownLeader(resp, false)
    88  	header = resp.Header().Get("X-Nomad-KnownLeader")
    89  	if header != "false" {
    90  		t.Fatalf("Bad: %v", header)
    91  	}
    92  }
    93  
    94  func TestSetLastContact(t *testing.T) {
    95  	t.Parallel()
    96  	resp := httptest.NewRecorder()
    97  	setLastContact(resp, 123456*time.Microsecond)
    98  	header := resp.Header().Get("X-Nomad-LastContact")
    99  	if header != "123" {
   100  		t.Fatalf("Bad: %v", header)
   101  	}
   102  }
   103  
   104  func TestSetMeta(t *testing.T) {
   105  	t.Parallel()
   106  	meta := structs.QueryMeta{
   107  		Index:       1000,
   108  		KnownLeader: true,
   109  		LastContact: 123456 * time.Microsecond,
   110  	}
   111  	resp := httptest.NewRecorder()
   112  	setMeta(resp, &meta)
   113  	header := resp.Header().Get("X-Nomad-Index")
   114  	if header != "1000" {
   115  		t.Fatalf("Bad: %v", header)
   116  	}
   117  	header = resp.Header().Get("X-Nomad-KnownLeader")
   118  	if header != "true" {
   119  		t.Fatalf("Bad: %v", header)
   120  	}
   121  	header = resp.Header().Get("X-Nomad-LastContact")
   122  	if header != "123" {
   123  		t.Fatalf("Bad: %v", header)
   124  	}
   125  }
   126  
   127  func TestSetHeaders(t *testing.T) {
   128  	t.Parallel()
   129  	s := makeHTTPServer(t, nil)
   130  	s.Agent.config.HTTPAPIResponseHeaders = map[string]string{"foo": "bar"}
   131  	defer s.Shutdown()
   132  
   133  	resp := httptest.NewRecorder()
   134  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   135  		return &structs.Job{Name: "foo"}, nil
   136  	}
   137  
   138  	req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
   139  	s.Server.wrap(handler)(resp, req)
   140  	header := resp.Header().Get("foo")
   141  
   142  	if header != "bar" {
   143  		t.Fatalf("expected header: %v, actual: %v", "bar", header)
   144  	}
   145  
   146  }
   147  
   148  func TestContentTypeIsJSON(t *testing.T) {
   149  	t.Parallel()
   150  	s := makeHTTPServer(t, nil)
   151  	defer s.Shutdown()
   152  
   153  	resp := httptest.NewRecorder()
   154  
   155  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   156  		return &structs.Job{Name: "foo"}, nil
   157  	}
   158  
   159  	req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
   160  	s.Server.wrap(handler)(resp, req)
   161  
   162  	contentType := resp.Header().Get("Content-Type")
   163  
   164  	if contentType != "application/json" {
   165  		t.Fatalf("Content-Type header was not 'application/json'")
   166  	}
   167  }
   168  
   169  func TestPrettyPrint(t *testing.T) {
   170  	t.Parallel()
   171  	testPrettyPrint("pretty=1", true, t)
   172  }
   173  
   174  func TestPrettyPrintOff(t *testing.T) {
   175  	t.Parallel()
   176  	testPrettyPrint("pretty=0", false, t)
   177  }
   178  
   179  func TestPrettyPrintBare(t *testing.T) {
   180  	t.Parallel()
   181  	testPrettyPrint("pretty", true, t)
   182  }
   183  
   184  func testPrettyPrint(pretty string, prettyFmt bool, t *testing.T) {
   185  	s := makeHTTPServer(t, nil)
   186  	defer s.Shutdown()
   187  
   188  	r := &structs.Job{Name: "foo"}
   189  
   190  	resp := httptest.NewRecorder()
   191  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   192  		return r, nil
   193  	}
   194  
   195  	urlStr := "/v1/job/foo?" + pretty
   196  	req, _ := http.NewRequest("GET", urlStr, nil)
   197  	s.Server.wrap(handler)(resp, req)
   198  
   199  	var expected bytes.Buffer
   200  	var err error
   201  	if prettyFmt {
   202  		enc := codec.NewEncoder(&expected, structs.JsonHandlePretty)
   203  		err = enc.Encode(r)
   204  		expected.WriteByte('\n')
   205  	} else {
   206  		enc := codec.NewEncoder(&expected, structs.JsonHandle)
   207  		err = enc.Encode(r)
   208  	}
   209  	if err != nil {
   210  		t.Fatalf("failed to encode: %v", err)
   211  	}
   212  	actual, err := ioutil.ReadAll(resp.Body)
   213  	if err != nil {
   214  		t.Fatalf("err: %s", err)
   215  	}
   216  
   217  	if !bytes.Equal(expected.Bytes(), actual) {
   218  		t.Fatalf("bad:\nexpected:\t%q\nactual:\t\t%q", expected.String(), string(actual))
   219  	}
   220  }
   221  
   222  func TestPermissionDenied(t *testing.T) {
   223  	s := makeHTTPServer(t, func(c *Config) {
   224  		c.ACL.Enabled = true
   225  	})
   226  	defer s.Shutdown()
   227  
   228  	{
   229  		resp := httptest.NewRecorder()
   230  		handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   231  			return nil, structs.ErrPermissionDenied
   232  		}
   233  
   234  		req, _ := http.NewRequest("GET", "/v1/job/foo", nil)
   235  		s.Server.wrap(handler)(resp, req)
   236  		assert.Equal(t, resp.Code, 403)
   237  	}
   238  
   239  	// When remote RPC is used the errors have "rpc error: " prependend
   240  	{
   241  		resp := httptest.NewRecorder()
   242  		handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   243  			return nil, fmt.Errorf("rpc error: %v", structs.ErrPermissionDenied)
   244  		}
   245  
   246  		req, _ := http.NewRequest("GET", "/v1/job/foo", nil)
   247  		s.Server.wrap(handler)(resp, req)
   248  		assert.Equal(t, resp.Code, 403)
   249  	}
   250  }
   251  
   252  func TestTokenNotFound(t *testing.T) {
   253  	s := makeHTTPServer(t, func(c *Config) {
   254  		c.ACL.Enabled = true
   255  	})
   256  	defer s.Shutdown()
   257  
   258  	resp := httptest.NewRecorder()
   259  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   260  		return nil, structs.ErrTokenNotFound
   261  	}
   262  
   263  	urlStr := "/v1/job/foo"
   264  	req, _ := http.NewRequest("GET", urlStr, nil)
   265  	s.Server.wrap(handler)(resp, req)
   266  	assert.Equal(t, resp.Code, 403)
   267  }
   268  
   269  func TestParseWait(t *testing.T) {
   270  	t.Parallel()
   271  	resp := httptest.NewRecorder()
   272  	var b structs.QueryOptions
   273  
   274  	req, err := http.NewRequest("GET",
   275  		"/v1/catalog/nodes?wait=60s&index=1000", nil)
   276  	if err != nil {
   277  		t.Fatalf("err: %v", err)
   278  	}
   279  
   280  	if d := parseWait(resp, req, &b); d {
   281  		t.Fatalf("unexpected done")
   282  	}
   283  
   284  	if b.MinQueryIndex != 1000 {
   285  		t.Fatalf("Bad: %v", b)
   286  	}
   287  	if b.MaxQueryTime != 60*time.Second {
   288  		t.Fatalf("Bad: %v", b)
   289  	}
   290  }
   291  
   292  func TestParseWait_InvalidTime(t *testing.T) {
   293  	t.Parallel()
   294  	resp := httptest.NewRecorder()
   295  	var b structs.QueryOptions
   296  
   297  	req, err := http.NewRequest("GET",
   298  		"/v1/catalog/nodes?wait=60foo&index=1000", nil)
   299  	if err != nil {
   300  		t.Fatalf("err: %v", err)
   301  	}
   302  
   303  	if d := parseWait(resp, req, &b); !d {
   304  		t.Fatalf("expected done")
   305  	}
   306  
   307  	if resp.Code != 400 {
   308  		t.Fatalf("bad code: %v", resp.Code)
   309  	}
   310  }
   311  
   312  func TestParseWait_InvalidIndex(t *testing.T) {
   313  	t.Parallel()
   314  	resp := httptest.NewRecorder()
   315  	var b structs.QueryOptions
   316  
   317  	req, err := http.NewRequest("GET",
   318  		"/v1/catalog/nodes?wait=60s&index=foo", nil)
   319  	if err != nil {
   320  		t.Fatalf("err: %v", err)
   321  	}
   322  
   323  	if d := parseWait(resp, req, &b); !d {
   324  		t.Fatalf("expected done")
   325  	}
   326  
   327  	if resp.Code != 400 {
   328  		t.Fatalf("bad code: %v", resp.Code)
   329  	}
   330  }
   331  
   332  func TestParseConsistency(t *testing.T) {
   333  	t.Parallel()
   334  	var b structs.QueryOptions
   335  
   336  	req, err := http.NewRequest("GET",
   337  		"/v1/catalog/nodes?stale", nil)
   338  	if err != nil {
   339  		t.Fatalf("err: %v", err)
   340  	}
   341  
   342  	parseConsistency(req, &b)
   343  	if !b.AllowStale {
   344  		t.Fatalf("Bad: %v", b)
   345  	}
   346  
   347  	b = structs.QueryOptions{}
   348  	req, err = http.NewRequest("GET",
   349  		"/v1/catalog/nodes?consistent", nil)
   350  	if err != nil {
   351  		t.Fatalf("err: %v", err)
   352  	}
   353  
   354  	parseConsistency(req, &b)
   355  	if b.AllowStale {
   356  		t.Fatalf("Bad: %v", b)
   357  	}
   358  }
   359  
   360  func TestParseRegion(t *testing.T) {
   361  	t.Parallel()
   362  	s := makeHTTPServer(t, nil)
   363  	defer s.Shutdown()
   364  
   365  	req, err := http.NewRequest("GET",
   366  		"/v1/jobs?region=foo", nil)
   367  	if err != nil {
   368  		t.Fatalf("err: %v", err)
   369  	}
   370  
   371  	var region string
   372  	s.Server.parseRegion(req, &region)
   373  	if region != "foo" {
   374  		t.Fatalf("bad %s", region)
   375  	}
   376  
   377  	region = ""
   378  	req, err = http.NewRequest("GET", "/v1/jobs", nil)
   379  	if err != nil {
   380  		t.Fatalf("err: %v", err)
   381  	}
   382  
   383  	s.Server.parseRegion(req, &region)
   384  	if region != "global" {
   385  		t.Fatalf("bad %s", region)
   386  	}
   387  }
   388  
   389  func TestParseToken(t *testing.T) {
   390  	t.Parallel()
   391  	s := makeHTTPServer(t, nil)
   392  	defer s.Shutdown()
   393  
   394  	req, err := http.NewRequest("GET", "/v1/jobs", nil)
   395  	req.Header.Add("X-Nomad-Token", "foobar")
   396  	if err != nil {
   397  		t.Fatalf("err: %v", err)
   398  	}
   399  
   400  	var token string
   401  	s.Server.parseToken(req, &token)
   402  	if token != "foobar" {
   403  		t.Fatalf("bad %s", token)
   404  	}
   405  }
   406  
   407  // TestHTTP_VerifyHTTPSClient asserts that a client certificate signed by the
   408  // appropriate CA is required when VerifyHTTPSClient=true.
   409  func TestHTTP_VerifyHTTPSClient(t *testing.T) {
   410  	t.Parallel()
   411  	const (
   412  		cafile  = "../../helper/tlsutil/testdata/ca.pem"
   413  		foocert = "../../helper/tlsutil/testdata/nomad-foo.pem"
   414  		fookey  = "../../helper/tlsutil/testdata/nomad-foo-key.pem"
   415  	)
   416  	s := makeHTTPServer(t, func(c *Config) {
   417  		c.Region = "foo" // match the region on foocert
   418  		c.TLSConfig = &config.TLSConfig{
   419  			EnableHTTP:        true,
   420  			VerifyHTTPSClient: true,
   421  			CAFile:            cafile,
   422  			CertFile:          foocert,
   423  			KeyFile:           fookey,
   424  		}
   425  	})
   426  	defer s.Shutdown()
   427  
   428  	reqURL := fmt.Sprintf("https://%s/v1/agent/self", s.Agent.config.AdvertiseAddrs.HTTP)
   429  
   430  	// FAIL: Requests that expect 127.0.0.1 as the name should fail
   431  	resp, err := http.Get(reqURL)
   432  	if err == nil {
   433  		resp.Body.Close()
   434  		t.Fatalf("expected non-nil error but received: %v", resp.StatusCode)
   435  	}
   436  	urlErr, ok := err.(*url.Error)
   437  	if !ok {
   438  		t.Fatalf("expected a *url.Error but received: %T -> %v", err, err)
   439  	}
   440  	hostErr, ok := urlErr.Err.(x509.HostnameError)
   441  	if !ok {
   442  		t.Fatalf("expected a x509.HostnameError but received: %T -> %v", urlErr.Err, urlErr.Err)
   443  	}
   444  	if expected := "127.0.0.1"; hostErr.Host != expected {
   445  		t.Fatalf("expected hostname on error to be %q but found %q", expected, hostErr.Host)
   446  	}
   447  
   448  	// FAIL: Requests that specify a valid hostname but not the CA should
   449  	// fail
   450  	tlsConf := &tls.Config{
   451  		ServerName: "client.regionFoo.nomad",
   452  	}
   453  	transport := &http.Transport{TLSClientConfig: tlsConf}
   454  	client := &http.Client{Transport: transport}
   455  	req, err := http.NewRequest("GET", reqURL, nil)
   456  	if err != nil {
   457  		t.Fatalf("error creating request: %v", err)
   458  	}
   459  	resp, err = client.Do(req)
   460  	if err == nil {
   461  		resp.Body.Close()
   462  		t.Fatalf("expected non-nil error but received: %v", resp.StatusCode)
   463  	}
   464  	urlErr, ok = err.(*url.Error)
   465  	if !ok {
   466  		t.Fatalf("expected a *url.Error but received: %T -> %v", err, err)
   467  	}
   468  	_, ok = urlErr.Err.(x509.UnknownAuthorityError)
   469  	if !ok {
   470  		t.Fatalf("expected a x509.UnknownAuthorityError but received: %T -> %v", urlErr.Err, urlErr.Err)
   471  	}
   472  
   473  	// FAIL: Requests that specify a valid hostname and CA cert but lack a
   474  	// client certificate should fail
   475  	cacertBytes, err := ioutil.ReadFile(cafile)
   476  	if err != nil {
   477  		t.Fatalf("error reading cacert: %v", err)
   478  	}
   479  	tlsConf.RootCAs = x509.NewCertPool()
   480  	tlsConf.RootCAs.AppendCertsFromPEM(cacertBytes)
   481  	req, err = http.NewRequest("GET", reqURL, nil)
   482  	if err != nil {
   483  		t.Fatalf("error creating request: %v", err)
   484  	}
   485  	resp, err = client.Do(req)
   486  	if err == nil {
   487  		resp.Body.Close()
   488  		t.Fatalf("expected non-nil error but received: %v", resp.StatusCode)
   489  	}
   490  	urlErr, ok = err.(*url.Error)
   491  	if !ok {
   492  		t.Fatalf("expected a *url.Error but received: %T -> %v", err, err)
   493  	}
   494  	opErr, ok := urlErr.Err.(*net.OpError)
   495  	if !ok {
   496  		t.Fatalf("expected a *net.OpErr but received: %T -> %v", urlErr.Err, urlErr.Err)
   497  	}
   498  	const badCertificate = "tls: bad certificate" // from crypto/tls/alert.go:52 and RFC 5246 ยง A.3
   499  	if opErr.Err.Error() != badCertificate {
   500  		t.Fatalf("expected tls.alert bad_certificate but received: %q", opErr.Err.Error())
   501  	}
   502  
   503  	// PASS: Requests that specify a valid hostname, CA cert, and client
   504  	// certificate succeed.
   505  	tlsConf.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
   506  		c, err := tls.LoadX509KeyPair(foocert, fookey)
   507  		if err != nil {
   508  			return nil, err
   509  		}
   510  		return &c, nil
   511  	}
   512  	transport = &http.Transport{TLSClientConfig: tlsConf}
   513  	client = &http.Client{Transport: transport}
   514  	req, err = http.NewRequest("GET", reqURL, nil)
   515  	if err != nil {
   516  		t.Fatalf("error creating request: %v", err)
   517  	}
   518  	resp, err = client.Do(req)
   519  	if err != nil {
   520  		t.Fatalf("unexpected error: %v", err)
   521  	}
   522  	resp.Body.Close()
   523  	if resp.StatusCode != 200 {
   524  		t.Fatalf("expected 200 status code but got: %d", resp.StatusCode)
   525  	}
   526  }
   527  
   528  // assertIndex tests that X-Nomad-Index is set and non-zero
   529  func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) {
   530  	header := resp.Header().Get("X-Nomad-Index")
   531  	if header == "" || header == "0" {
   532  		t.Fatalf("Bad: %v", header)
   533  	}
   534  }
   535  
   536  // checkIndex is like assertIndex but returns an error
   537  func checkIndex(resp *httptest.ResponseRecorder) error {
   538  	header := resp.Header().Get("X-Nomad-Index")
   539  	if header == "" || header == "0" {
   540  		return fmt.Errorf("Bad: %v", header)
   541  	}
   542  	return nil
   543  }
   544  
   545  func TestHTTP_VerifyHTTPSClient_AfterConfigReload(t *testing.T) {
   546  	t.Parallel()
   547  	assert := assert.New(t)
   548  
   549  	const (
   550  		cafile   = "../../helper/tlsutil/testdata/ca.pem"
   551  		foocert  = "../../helper/tlsutil/testdata/nomad-bad.pem"
   552  		fookey   = "../../helper/tlsutil/testdata/nomad-bad-key.pem"
   553  		foocert2 = "../../helper/tlsutil/testdata/nomad-foo.pem"
   554  		fookey2  = "../../helper/tlsutil/testdata/nomad-foo-key.pem"
   555  	)
   556  
   557  	agentConfig := &Config{
   558  		TLSConfig: &config.TLSConfig{
   559  			EnableHTTP:        true,
   560  			VerifyHTTPSClient: true,
   561  			CAFile:            cafile,
   562  			CertFile:          foocert,
   563  			KeyFile:           fookey,
   564  		},
   565  	}
   566  
   567  	newConfig := &Config{
   568  		TLSConfig: &config.TLSConfig{
   569  			EnableHTTP:        true,
   570  			VerifyHTTPSClient: true,
   571  			CAFile:            cafile,
   572  			CertFile:          foocert2,
   573  			KeyFile:           fookey2,
   574  		},
   575  	}
   576  
   577  	s := makeHTTPServer(t, func(c *Config) {
   578  		c.TLSConfig = agentConfig.TLSConfig
   579  	})
   580  	defer s.Shutdown()
   581  
   582  	// Make an initial request that should fail.
   583  	// Requests that specify a valid hostname, CA cert, and client
   584  	// certificate succeed.
   585  	tlsConf := &tls.Config{
   586  		ServerName: "client.regionFoo.nomad",
   587  		RootCAs:    x509.NewCertPool(),
   588  		GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
   589  			c, err := tls.LoadX509KeyPair(foocert, fookey)
   590  			if err != nil {
   591  				return nil, err
   592  			}
   593  			return &c, nil
   594  		},
   595  	}
   596  
   597  	// HTTPS request should succeed
   598  	httpsReqURL := fmt.Sprintf("https://%s/v1/agent/self", s.Agent.config.AdvertiseAddrs.HTTP)
   599  
   600  	cacertBytes, err := ioutil.ReadFile(cafile)
   601  	assert.Nil(err)
   602  	tlsConf.RootCAs.AppendCertsFromPEM(cacertBytes)
   603  
   604  	transport := &http.Transport{TLSClientConfig: tlsConf}
   605  	client := &http.Client{Transport: transport}
   606  	req, err := http.NewRequest("GET", httpsReqURL, nil)
   607  	assert.Nil(err)
   608  
   609  	// Check that we get an error that the certificate isn't valid for the
   610  	// region we are contacting.
   611  	_, err = client.Do(req)
   612  	assert.Contains(err.Error(), "certificate is valid for")
   613  
   614  	// Reload the TLS configuration==
   615  	assert.Nil(s.Agent.Reload(newConfig))
   616  
   617  	// Requests that specify a valid hostname, CA cert, and client
   618  	// certificate succeed.
   619  	tlsConf = &tls.Config{
   620  		ServerName: "client.regionFoo.nomad",
   621  		RootCAs:    x509.NewCertPool(),
   622  		GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
   623  			c, err := tls.LoadX509KeyPair(foocert2, fookey2)
   624  			if err != nil {
   625  				return nil, err
   626  			}
   627  			return &c, nil
   628  		},
   629  	}
   630  
   631  	cacertBytes, err = ioutil.ReadFile(cafile)
   632  	assert.Nil(err)
   633  	tlsConf.RootCAs.AppendCertsFromPEM(cacertBytes)
   634  
   635  	transport = &http.Transport{TLSClientConfig: tlsConf}
   636  	client = &http.Client{Transport: transport}
   637  	req, err = http.NewRequest("GET", httpsReqURL, nil)
   638  	assert.Nil(err)
   639  
   640  	resp, err := client.Do(req)
   641  	if assert.Nil(err) {
   642  		resp.Body.Close()
   643  		assert.Equal(resp.StatusCode, 200)
   644  	}
   645  }
   646  
   647  // getIndex parses X-Nomad-Index
   648  func getIndex(t *testing.T, resp *httptest.ResponseRecorder) uint64 {
   649  	header := resp.Header().Get("X-Nomad-Index")
   650  	if header == "" {
   651  		t.Fatalf("Bad: %v", header)
   652  	}
   653  	val, err := strconv.Atoi(header)
   654  	if err != nil {
   655  		t.Fatalf("Bad: %v", header)
   656  	}
   657  	return uint64(val)
   658  }
   659  
   660  func httpTest(t testing.TB, cb func(c *Config), f func(srv *TestAgent)) {
   661  	s := makeHTTPServer(t, cb)
   662  	defer s.Shutdown()
   663  	testutil.WaitForLeader(t, s.Agent.RPC)
   664  	f(s)
   665  }
   666  
   667  func httpACLTest(t testing.TB, cb func(c *Config), f func(srv *TestAgent)) {
   668  	s := makeHTTPServer(t, func(c *Config) {
   669  		c.ACL.Enabled = true
   670  		if cb != nil {
   671  			cb(c)
   672  		}
   673  	})
   674  	defer s.Shutdown()
   675  	testutil.WaitForLeader(t, s.Agent.RPC)
   676  	f(s)
   677  }
   678  
   679  func setToken(req *http.Request, token *structs.ACLToken) {
   680  	req.Header.Set("X-Nomad-Token", token.SecretID)
   681  }
   682  
   683  func encodeReq(obj interface{}) io.ReadCloser {
   684  	buf := bytes.NewBuffer(nil)
   685  	enc := json.NewEncoder(buf)
   686  	enc.Encode(obj)
   687  	return ioutil.NopCloser(buf)
   688  }