2015-09-06 00:06:05 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2017-05-02 23:48:16 +00:00
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2015-09-06 00:06:05 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2015-09-06 02:08:47 +00:00
|
|
|
"io"
|
2015-09-06 00:06:05 +00:00
|
|
|
"io/ioutil"
|
2017-05-02 23:48:16 +00:00
|
|
|
"net"
|
2015-09-06 00:06:05 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2017-05-02 23:48:16 +00:00
|
|
|
"net/url"
|
2015-09-06 00:06:05 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2016-05-18 01:05:00 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
2015-09-06 00:06:05 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
2017-05-02 23:48:16 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/structs/config"
|
2015-09-06 01:00:30 +00:00
|
|
|
"github.com/hashicorp/nomad/testutil"
|
2017-10-12 21:07:52 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2019-11-11 17:07:42 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2017-08-16 18:01:12 +00:00
|
|
|
"github.com/ugorji/go/codec"
|
2015-09-06 00:06:05 +00:00
|
|
|
)
|
|
|
|
|
2017-04-10 12:15:40 +00:00
|
|
|
// makeHTTPServer returns a test server whose logs will be written to
|
2016-05-18 01:05:00 +00:00
|
|
|
// the passed writer. If the writer is nil, the logs are written to stderr.
|
2017-07-20 05:14:36 +00:00
|
|
|
func makeHTTPServer(t testing.TB, cb func(c *Config)) *TestAgent {
|
2017-10-19 04:45:18 +00:00
|
|
|
return NewTestAgent(t, t.Name(), cb)
|
2015-09-06 00:06:05 +00:00
|
|
|
}
|
|
|
|
|
2016-05-18 01:05:00 +00:00
|
|
|
func BenchmarkHTTPRequests(b *testing.B) {
|
2017-04-10 12:15:40 +00:00
|
|
|
s := makeHTTPServer(b, func(c *Config) {
|
2016-05-18 01:05:00 +00:00
|
|
|
c.Client.Enabled = false
|
|
|
|
})
|
2017-07-20 05:14:36 +00:00
|
|
|
defer s.Shutdown()
|
2016-05-18 01:05:00 +00:00
|
|
|
|
|
|
|
job := mock.Job()
|
|
|
|
var allocs []*structs.Allocation
|
|
|
|
count := 1000
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
alloc := mock.Alloc()
|
|
|
|
alloc.Job = job
|
|
|
|
alloc.JobID = job.ID
|
|
|
|
alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
|
|
|
|
allocs = append(allocs, alloc)
|
|
|
|
}
|
|
|
|
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return allocs[:count], nil
|
|
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
for pb.Next() {
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
|
|
|
|
s.Server.wrap(handler)(resp, req)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-11-11 17:07:42 +00:00
|
|
|
// TestRootFallthrough tests rootFallthrough handler to
|
|
|
|
// verify redirect and 404 behavior
|
|
|
|
func TestRootFallthrough(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
desc string
|
|
|
|
path string
|
2019-11-12 19:28:21 +00:00
|
|
|
expectedPath string
|
2019-11-11 17:07:42 +00:00
|
|
|
expectedCode int
|
|
|
|
}{
|
|
|
|
{
|
2019-11-11 17:11:15 +00:00
|
|
|
desc: "unknown endpoint 404s",
|
2019-11-11 17:07:42 +00:00
|
|
|
path: "/v1/unknown/endpoint",
|
|
|
|
expectedCode: 404,
|
|
|
|
},
|
|
|
|
{
|
2019-11-11 17:11:15 +00:00
|
|
|
desc: "root path redirects to ui",
|
2019-11-11 17:07:42 +00:00
|
|
|
path: "/",
|
2019-11-12 19:28:21 +00:00
|
|
|
expectedPath: "/ui/",
|
2019-11-11 17:07:42 +00:00
|
|
|
expectedCode: 307,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
s := makeHTTPServer(t, nil)
|
|
|
|
defer s.Shutdown()
|
|
|
|
|
2019-11-12 19:28:21 +00:00
|
|
|
// setup a client that doesn't follow redirects
|
|
|
|
client := &http.Client{
|
|
|
|
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-11-11 17:07:42 +00:00
|
|
|
for _, tc := range cases {
|
|
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
|
|
|
|
|
|
reqURL := fmt.Sprintf("http://%s%s", s.Agent.config.AdvertiseAddrs.HTTP, tc.path)
|
|
|
|
|
|
|
|
resp, err := client.Get(reqURL)
|
|
|
|
require.NoError(t, err)
|
2019-11-11 17:11:15 +00:00
|
|
|
require.Equal(t, tc.expectedCode, resp.StatusCode)
|
|
|
|
|
2019-11-12 19:28:21 +00:00
|
|
|
if tc.expectedPath != "" {
|
2019-11-11 17:11:15 +00:00
|
|
|
loc, err := resp.Location()
|
|
|
|
require.NoError(t, err)
|
2019-11-12 19:28:21 +00:00
|
|
|
require.Equal(t, tc.expectedPath, loc.Path)
|
2019-11-11 17:11:15 +00:00
|
|
|
}
|
2019-11-11 17:07:42 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-06 00:06:05 +00:00
|
|
|
func TestSetIndex(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setIndex(resp, 1000)
|
|
|
|
header := resp.Header().Get("X-Nomad-Index")
|
|
|
|
if header != "1000" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
setIndex(resp, 2000)
|
|
|
|
if v := resp.Header()["X-Nomad-Index"]; len(v) != 1 {
|
|
|
|
t.Fatalf("bad: %#v", v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetKnownLeader(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setKnownLeader(resp, true)
|
|
|
|
header := resp.Header().Get("X-Nomad-KnownLeader")
|
|
|
|
if header != "true" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
resp = httptest.NewRecorder()
|
|
|
|
setKnownLeader(resp, false)
|
|
|
|
header = resp.Header().Get("X-Nomad-KnownLeader")
|
|
|
|
if header != "false" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetLastContact(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setLastContact(resp, 123456*time.Microsecond)
|
|
|
|
header := resp.Header().Get("X-Nomad-LastContact")
|
|
|
|
if header != "123" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetMeta(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
meta := structs.QueryMeta{
|
|
|
|
Index: 1000,
|
|
|
|
KnownLeader: true,
|
|
|
|
LastContact: 123456 * time.Microsecond,
|
|
|
|
}
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setMeta(resp, &meta)
|
|
|
|
header := resp.Header().Get("X-Nomad-Index")
|
|
|
|
if header != "1000" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
header = resp.Header().Get("X-Nomad-KnownLeader")
|
|
|
|
if header != "true" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
header = resp.Header().Get("X-Nomad-LastContact")
|
|
|
|
if header != "123" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-22 18:52:20 +00:00
|
|
|
func TestSetHeaders(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2016-01-22 18:52:20 +00:00
|
|
|
s := makeHTTPServer(t, nil)
|
|
|
|
s.Agent.config.HTTPAPIResponseHeaders = map[string]string{"foo": "bar"}
|
2017-07-20 05:14:36 +00:00
|
|
|
defer s.Shutdown()
|
2016-01-22 18:52:20 +00:00
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return &structs.Job{Name: "foo"}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
|
|
|
|
s.Server.wrap(handler)(resp, req)
|
|
|
|
header := resp.Header().Get("foo")
|
|
|
|
|
|
|
|
if header != "bar" {
|
|
|
|
t.Fatalf("expected header: %v, actual: %v", "bar", header)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-09-06 00:06:05 +00:00
|
|
|
func TestContentTypeIsJSON(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
s := makeHTTPServer(t, nil)
|
2017-07-20 05:14:36 +00:00
|
|
|
defer s.Shutdown()
|
2015-09-06 00:06:05 +00:00
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return &structs.Job{Name: "foo"}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
|
|
|
|
s.Server.wrap(handler)(resp, req)
|
|
|
|
|
|
|
|
contentType := resp.Header().Get("Content-Type")
|
|
|
|
|
|
|
|
if contentType != "application/json" {
|
|
|
|
t.Fatalf("Content-Type header was not 'application/json'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPrettyPrint(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2016-05-11 17:55:01 +00:00
|
|
|
testPrettyPrint("pretty=1", true, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPrettyPrintOff(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2016-05-11 17:55:01 +00:00
|
|
|
testPrettyPrint("pretty=0", false, t)
|
2015-09-06 00:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestPrettyPrintBare(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2016-05-11 17:55:01 +00:00
|
|
|
testPrettyPrint("pretty", true, t)
|
2015-09-06 00:06:05 +00:00
|
|
|
}
|
|
|
|
|
2016-05-11 17:55:01 +00:00
|
|
|
func testPrettyPrint(pretty string, prettyFmt bool, t *testing.T) {
|
2015-09-06 00:06:05 +00:00
|
|
|
s := makeHTTPServer(t, nil)
|
2017-07-20 05:14:36 +00:00
|
|
|
defer s.Shutdown()
|
2015-09-06 00:06:05 +00:00
|
|
|
|
|
|
|
r := &structs.Job{Name: "foo"}
|
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
urlStr := "/v1/job/foo?" + pretty
|
|
|
|
req, _ := http.NewRequest("GET", urlStr, nil)
|
|
|
|
s.Server.wrap(handler)(resp, req)
|
|
|
|
|
2017-08-16 18:01:12 +00:00
|
|
|
var expected bytes.Buffer
|
|
|
|
var err error
|
2016-05-11 17:55:01 +00:00
|
|
|
if prettyFmt {
|
2017-08-16 18:01:12 +00:00
|
|
|
enc := codec.NewEncoder(&expected, structs.JsonHandlePretty)
|
|
|
|
err = enc.Encode(r)
|
|
|
|
expected.WriteByte('\n')
|
2016-05-11 22:46:08 +00:00
|
|
|
} else {
|
2017-08-16 18:01:12 +00:00
|
|
|
enc := codec.NewEncoder(&expected, structs.JsonHandle)
|
|
|
|
err = enc.Encode(r)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to encode: %v", err)
|
2016-05-11 17:55:01 +00:00
|
|
|
}
|
2015-09-06 00:06:05 +00:00
|
|
|
actual, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
2017-08-16 18:01:12 +00:00
|
|
|
if !bytes.Equal(expected.Bytes(), actual) {
|
|
|
|
t.Fatalf("bad:\nexpected:\t%q\nactual:\t\t%q", expected.String(), string(actual))
|
2015-09-06 00:06:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-12 21:07:52 +00:00
|
|
|
func TestPermissionDenied(t *testing.T) {
|
|
|
|
s := makeHTTPServer(t, func(c *Config) {
|
|
|
|
c.ACL.Enabled = true
|
|
|
|
})
|
|
|
|
defer s.Shutdown()
|
|
|
|
|
2018-01-05 22:12:52 +00:00
|
|
|
{
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return nil, structs.ErrPermissionDenied
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/job/foo", nil)
|
|
|
|
s.Server.wrap(handler)(resp, req)
|
|
|
|
assert.Equal(t, resp.Code, 403)
|
2017-10-12 21:07:52 +00:00
|
|
|
}
|
|
|
|
|
2018-01-05 22:12:52 +00:00
|
|
|
// When remote RPC is used the errors have "rpc error: " prependend
|
|
|
|
{
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return nil, fmt.Errorf("rpc error: %v", structs.ErrPermissionDenied)
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/job/foo", nil)
|
|
|
|
s.Server.wrap(handler)(resp, req)
|
|
|
|
assert.Equal(t, resp.Code, 403)
|
|
|
|
}
|
2017-10-12 21:07:52 +00:00
|
|
|
}
|
|
|
|
|
2017-10-12 22:39:05 +00:00
|
|
|
func TestTokenNotFound(t *testing.T) {
|
|
|
|
s := makeHTTPServer(t, func(c *Config) {
|
|
|
|
c.ACL.Enabled = true
|
|
|
|
})
|
|
|
|
defer s.Shutdown()
|
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return nil, structs.ErrTokenNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
urlStr := "/v1/job/foo"
|
|
|
|
req, _ := http.NewRequest("GET", urlStr, nil)
|
|
|
|
s.Server.wrap(handler)(resp, req)
|
|
|
|
assert.Equal(t, resp.Code, 403)
|
|
|
|
}
|
|
|
|
|
2015-09-06 00:06:05 +00:00
|
|
|
func TestParseWait(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
var b structs.QueryOptions
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?wait=60s&index=1000", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseWait(resp, req, &b); d {
|
|
|
|
t.Fatalf("unexpected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.MinQueryIndex != 1000 {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
if b.MaxQueryTime != 60*time.Second {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseWait_InvalidTime(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
var b structs.QueryOptions
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?wait=60foo&index=1000", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseWait(resp, req, &b); !d {
|
|
|
|
t.Fatalf("expected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Code != 400 {
|
|
|
|
t.Fatalf("bad code: %v", resp.Code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseWait_InvalidIndex(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
var b structs.QueryOptions
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?wait=60s&index=foo", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseWait(resp, req, &b); !d {
|
|
|
|
t.Fatalf("expected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Code != 400 {
|
|
|
|
t.Fatalf("bad code: %v", resp.Code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseConsistency(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
var b structs.QueryOptions
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?stale", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
parseConsistency(req, &b)
|
|
|
|
if !b.AllowStale {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
|
|
|
|
b = structs.QueryOptions{}
|
|
|
|
req, err = http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?consistent", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
parseConsistency(req, &b)
|
|
|
|
if b.AllowStale {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseRegion(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2015-09-06 00:06:05 +00:00
|
|
|
s := makeHTTPServer(t, nil)
|
2017-07-20 05:14:36 +00:00
|
|
|
defer s.Shutdown()
|
2015-09-06 00:06:05 +00:00
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/jobs?region=foo", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var region string
|
|
|
|
s.Server.parseRegion(req, ®ion)
|
|
|
|
if region != "foo" {
|
|
|
|
t.Fatalf("bad %s", region)
|
|
|
|
}
|
|
|
|
|
|
|
|
region = ""
|
|
|
|
req, err = http.NewRequest("GET", "/v1/jobs", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Server.parseRegion(req, ®ion)
|
2015-09-14 01:18:40 +00:00
|
|
|
if region != "global" {
|
2015-09-06 00:06:05 +00:00
|
|
|
t.Fatalf("bad %s", region)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-21 03:13:05 +00:00
|
|
|
func TestParseToken(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
s := makeHTTPServer(t, nil)
|
|
|
|
defer s.Shutdown()
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", "/v1/jobs", nil)
|
|
|
|
req.Header.Add("X-Nomad-Token", "foobar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var token string
|
|
|
|
s.Server.parseToken(req, &token)
|
|
|
|
if token != "foobar" {
|
|
|
|
t.Fatalf("bad %s", token)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-02 23:48:16 +00:00
|
|
|
// TestHTTP_VerifyHTTPSClient asserts that a client certificate signed by the
|
|
|
|
// appropriate CA is required when VerifyHTTPSClient=true.
|
|
|
|
func TestHTTP_VerifyHTTPSClient(t *testing.T) {
|
2017-07-20 05:42:15 +00:00
|
|
|
t.Parallel()
|
2017-05-02 23:48:16 +00:00
|
|
|
const (
|
|
|
|
cafile = "../../helper/tlsutil/testdata/ca.pem"
|
|
|
|
foocert = "../../helper/tlsutil/testdata/nomad-foo.pem"
|
|
|
|
fookey = "../../helper/tlsutil/testdata/nomad-foo-key.pem"
|
|
|
|
)
|
|
|
|
s := makeHTTPServer(t, func(c *Config) {
|
|
|
|
c.Region = "foo" // match the region on foocert
|
|
|
|
c.TLSConfig = &config.TLSConfig{
|
|
|
|
EnableHTTP: true,
|
|
|
|
VerifyHTTPSClient: true,
|
|
|
|
CAFile: cafile,
|
|
|
|
CertFile: foocert,
|
|
|
|
KeyFile: fookey,
|
|
|
|
}
|
|
|
|
})
|
2017-07-20 05:14:36 +00:00
|
|
|
defer s.Shutdown()
|
2017-05-02 23:48:16 +00:00
|
|
|
|
|
|
|
reqURL := fmt.Sprintf("https://%s/v1/agent/self", s.Agent.config.AdvertiseAddrs.HTTP)
|
|
|
|
|
|
|
|
// FAIL: Requests that expect 127.0.0.1 as the name should fail
|
|
|
|
resp, err := http.Get(reqURL)
|
|
|
|
if err == nil {
|
|
|
|
resp.Body.Close()
|
|
|
|
t.Fatalf("expected non-nil error but received: %v", resp.StatusCode)
|
|
|
|
}
|
|
|
|
urlErr, ok := err.(*url.Error)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected a *url.Error but received: %T -> %v", err, err)
|
|
|
|
}
|
|
|
|
hostErr, ok := urlErr.Err.(x509.HostnameError)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected a x509.HostnameError but received: %T -> %v", urlErr.Err, urlErr.Err)
|
|
|
|
}
|
|
|
|
if expected := "127.0.0.1"; hostErr.Host != expected {
|
|
|
|
t.Fatalf("expected hostname on error to be %q but found %q", expected, hostErr.Host)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FAIL: Requests that specify a valid hostname but not the CA should
|
|
|
|
// fail
|
|
|
|
tlsConf := &tls.Config{
|
|
|
|
ServerName: "client.regionFoo.nomad",
|
|
|
|
}
|
|
|
|
transport := &http.Transport{TLSClientConfig: tlsConf}
|
|
|
|
client := &http.Client{Transport: transport}
|
|
|
|
req, err := http.NewRequest("GET", reqURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating request: %v", err)
|
|
|
|
}
|
|
|
|
resp, err = client.Do(req)
|
|
|
|
if err == nil {
|
|
|
|
resp.Body.Close()
|
|
|
|
t.Fatalf("expected non-nil error but received: %v", resp.StatusCode)
|
|
|
|
}
|
|
|
|
urlErr, ok = err.(*url.Error)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected a *url.Error but received: %T -> %v", err, err)
|
|
|
|
}
|
|
|
|
_, ok = urlErr.Err.(x509.UnknownAuthorityError)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected a x509.UnknownAuthorityError but received: %T -> %v", urlErr.Err, urlErr.Err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FAIL: Requests that specify a valid hostname and CA cert but lack a
|
|
|
|
// client certificate should fail
|
|
|
|
cacertBytes, err := ioutil.ReadFile(cafile)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error reading cacert: %v", err)
|
|
|
|
}
|
|
|
|
tlsConf.RootCAs = x509.NewCertPool()
|
|
|
|
tlsConf.RootCAs.AppendCertsFromPEM(cacertBytes)
|
|
|
|
req, err = http.NewRequest("GET", reqURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating request: %v", err)
|
|
|
|
}
|
|
|
|
resp, err = client.Do(req)
|
|
|
|
if err == nil {
|
|
|
|
resp.Body.Close()
|
|
|
|
t.Fatalf("expected non-nil error but received: %v", resp.StatusCode)
|
|
|
|
}
|
|
|
|
urlErr, ok = err.(*url.Error)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected a *url.Error but received: %T -> %v", err, err)
|
|
|
|
}
|
|
|
|
opErr, ok := urlErr.Err.(*net.OpError)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected a *net.OpErr but received: %T -> %v", urlErr.Err, urlErr.Err)
|
|
|
|
}
|
|
|
|
const badCertificate = "tls: bad certificate" // from crypto/tls/alert.go:52 and RFC 5246 § A.3
|
|
|
|
if opErr.Err.Error() != badCertificate {
|
|
|
|
t.Fatalf("expected tls.alert bad_certificate but received: %q", opErr.Err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// PASS: Requests that specify a valid hostname, CA cert, and client
|
|
|
|
// certificate succeed.
|
|
|
|
tlsConf.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
|
|
|
c, err := tls.LoadX509KeyPair(foocert, fookey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &c, nil
|
|
|
|
}
|
2017-05-03 20:26:55 +00:00
|
|
|
transport = &http.Transport{TLSClientConfig: tlsConf}
|
|
|
|
client = &http.Client{Transport: transport}
|
2017-05-02 23:48:16 +00:00
|
|
|
req, err = http.NewRequest("GET", reqURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating request: %v", err)
|
|
|
|
}
|
|
|
|
resp, err = client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
t.Fatalf("expected 200 status code but got: %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 01:53:23 +00:00
|
|
|
func TestHTTP_VerifyHTTPSClient_AfterConfigReload(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
assert := assert.New(t)
|
|
|
|
|
|
|
|
const (
|
|
|
|
cafile = "../../helper/tlsutil/testdata/ca.pem"
|
|
|
|
foocert = "../../helper/tlsutil/testdata/nomad-bad.pem"
|
|
|
|
fookey = "../../helper/tlsutil/testdata/nomad-bad-key.pem"
|
|
|
|
foocert2 = "../../helper/tlsutil/testdata/nomad-foo.pem"
|
|
|
|
fookey2 = "../../helper/tlsutil/testdata/nomad-foo-key.pem"
|
|
|
|
)
|
|
|
|
|
|
|
|
agentConfig := &Config{
|
|
|
|
TLSConfig: &config.TLSConfig{
|
|
|
|
EnableHTTP: true,
|
|
|
|
VerifyHTTPSClient: true,
|
|
|
|
CAFile: cafile,
|
|
|
|
CertFile: foocert,
|
|
|
|
KeyFile: fookey,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
newConfig := &Config{
|
|
|
|
TLSConfig: &config.TLSConfig{
|
|
|
|
EnableHTTP: true,
|
|
|
|
VerifyHTTPSClient: true,
|
|
|
|
CAFile: cafile,
|
|
|
|
CertFile: foocert2,
|
|
|
|
KeyFile: fookey2,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
s := makeHTTPServer(t, func(c *Config) {
|
|
|
|
c.TLSConfig = agentConfig.TLSConfig
|
|
|
|
})
|
|
|
|
defer s.Shutdown()
|
|
|
|
|
|
|
|
// Make an initial request that should fail.
|
|
|
|
// Requests that specify a valid hostname, CA cert, and client
|
|
|
|
// certificate succeed.
|
|
|
|
tlsConf := &tls.Config{
|
|
|
|
ServerName: "client.regionFoo.nomad",
|
|
|
|
RootCAs: x509.NewCertPool(),
|
|
|
|
GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
|
|
|
c, err := tls.LoadX509KeyPair(foocert, fookey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &c, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// HTTPS request should succeed
|
|
|
|
httpsReqURL := fmt.Sprintf("https://%s/v1/agent/self", s.Agent.config.AdvertiseAddrs.HTTP)
|
|
|
|
|
|
|
|
cacertBytes, err := ioutil.ReadFile(cafile)
|
|
|
|
assert.Nil(err)
|
|
|
|
tlsConf.RootCAs.AppendCertsFromPEM(cacertBytes)
|
|
|
|
|
|
|
|
transport := &http.Transport{TLSClientConfig: tlsConf}
|
|
|
|
client := &http.Client{Transport: transport}
|
|
|
|
req, err := http.NewRequest("GET", httpsReqURL, nil)
|
|
|
|
assert.Nil(err)
|
|
|
|
|
|
|
|
// Check that we get an error that the certificate isn't valid for the
|
|
|
|
// region we are contacting.
|
|
|
|
_, err = client.Do(req)
|
|
|
|
assert.Contains(err.Error(), "certificate is valid for")
|
|
|
|
|
|
|
|
// Reload the TLS configuration==
|
|
|
|
assert.Nil(s.Agent.Reload(newConfig))
|
|
|
|
|
|
|
|
// Requests that specify a valid hostname, CA cert, and client
|
|
|
|
// certificate succeed.
|
|
|
|
tlsConf = &tls.Config{
|
|
|
|
ServerName: "client.regionFoo.nomad",
|
|
|
|
RootCAs: x509.NewCertPool(),
|
|
|
|
GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
|
|
|
c, err := tls.LoadX509KeyPair(foocert2, fookey2)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &c, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
cacertBytes, err = ioutil.ReadFile(cafile)
|
|
|
|
assert.Nil(err)
|
|
|
|
tlsConf.RootCAs.AppendCertsFromPEM(cacertBytes)
|
|
|
|
|
|
|
|
transport = &http.Transport{TLSClientConfig: tlsConf}
|
|
|
|
client = &http.Client{Transport: transport}
|
|
|
|
req, err = http.NewRequest("GET", httpsReqURL, nil)
|
|
|
|
assert.Nil(err)
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if assert.Nil(err) {
|
|
|
|
resp.Body.Close()
|
|
|
|
assert.Equal(resp.StatusCode, 200)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-20 05:14:36 +00:00
|
|
|
func httpTest(t testing.TB, cb func(c *Config), f func(srv *TestAgent)) {
|
2015-09-06 01:00:30 +00:00
|
|
|
s := makeHTTPServer(t, cb)
|
2017-07-20 05:14:36 +00:00
|
|
|
defer s.Shutdown()
|
2015-09-06 01:00:30 +00:00
|
|
|
testutil.WaitForLeader(t, s.Agent.RPC)
|
|
|
|
f(s)
|
|
|
|
}
|
2015-09-06 02:08:47 +00:00
|
|
|
|
2017-08-21 02:59:25 +00:00
|
|
|
func httpACLTest(t testing.TB, cb func(c *Config), f func(srv *TestAgent)) {
|
|
|
|
s := makeHTTPServer(t, func(c *Config) {
|
|
|
|
c.ACL.Enabled = true
|
|
|
|
if cb != nil {
|
|
|
|
cb(c)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
defer s.Shutdown()
|
|
|
|
testutil.WaitForLeader(t, s.Agent.RPC)
|
|
|
|
f(s)
|
|
|
|
}
|
|
|
|
|
2017-08-21 04:05:28 +00:00
|
|
|
func setToken(req *http.Request, token *structs.ACLToken) {
|
|
|
|
req.Header.Set("X-Nomad-Token", token.SecretID)
|
|
|
|
}
|
|
|
|
|
2015-09-06 02:08:47 +00:00
|
|
|
func encodeReq(obj interface{}) io.ReadCloser {
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
enc := json.NewEncoder(buf)
|
|
|
|
enc.Encode(obj)
|
|
|
|
return ioutil.NopCloser(buf)
|
|
|
|
}
|