agent: Adding HTTP endpoints for ACL tokens
This commit is contained in:
parent
f91d2608cb
commit
bd2db18c80
|
@ -109,3 +109,118 @@ func (s *HTTPServer) aclPolicyDelete(resp http.ResponseWriter, req *http.Request
|
|||
setIndex(resp, out.Index)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) ACLTokensRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if req.Method != "GET" {
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
args := structs.ACLTokenListRequest{}
|
||||
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var out structs.ACLTokenListResponse
|
||||
if err := s.agent.RPC("ACL.ListTokens", &args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setMeta(resp, &out.QueryMeta)
|
||||
if out.Tokens == nil {
|
||||
out.Tokens = make([]*structs.ACLTokenListStub, 0)
|
||||
}
|
||||
return out.Tokens, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) ACLTokenSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
accessor := strings.TrimPrefix(req.URL.Path, "/v1/acl/token")
|
||||
|
||||
// If there is no accessor, this must be a create
|
||||
if len(accessor) == 0 {
|
||||
if !(req.Method == "PUT" || req.Method == "POST") {
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
return s.aclTokenUpdate(resp, req, "")
|
||||
}
|
||||
|
||||
// Check if no accessor is given past the slash
|
||||
accessor = accessor[1:]
|
||||
if accessor == "" {
|
||||
return nil, CodedError(400, "Missing Token Accessor")
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
return s.aclTokenQuery(resp, req, accessor)
|
||||
case "PUT", "POST":
|
||||
return s.aclTokenUpdate(resp, req, accessor)
|
||||
case "DELETE":
|
||||
return s.aclTokenDelete(resp, req, accessor)
|
||||
default:
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) aclTokenQuery(resp http.ResponseWriter, req *http.Request,
|
||||
tokenAccessor string) (interface{}, error) {
|
||||
args := structs.ACLTokenSpecificRequest{
|
||||
AccessorID: tokenAccessor,
|
||||
}
|
||||
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var out structs.SingleACLTokenResponse
|
||||
if err := s.agent.RPC("ACL.GetToken", &args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setMeta(resp, &out.QueryMeta)
|
||||
if out.Token == nil {
|
||||
return nil, CodedError(404, "ACL token not found")
|
||||
}
|
||||
return out.Token, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) aclTokenUpdate(resp http.ResponseWriter, req *http.Request,
|
||||
tokenAccessor string) (interface{}, error) {
|
||||
// Parse the token
|
||||
var token structs.ACLToken
|
||||
if err := decodeBody(req, &token); err != nil {
|
||||
return nil, CodedError(500, err.Error())
|
||||
}
|
||||
|
||||
// Ensure the token accessor matches
|
||||
if tokenAccessor != "" && (token.AccessorID != tokenAccessor) {
|
||||
return nil, CodedError(400, "ACL token accessor does not match request path")
|
||||
}
|
||||
|
||||
// Format the request
|
||||
args := structs.ACLTokenUpsertRequest{
|
||||
Tokens: []*structs.ACLToken{&token},
|
||||
}
|
||||
s.parseRegion(req, &args.Region)
|
||||
|
||||
var out structs.GenericResponse
|
||||
if err := s.agent.RPC("ACL.UpsertTokens", &args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setIndex(resp, out.Index)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) aclTokenDelete(resp http.ResponseWriter, req *http.Request,
|
||||
tokenAccessor string) (interface{}, error) {
|
||||
|
||||
args := structs.ACLTokenDeleteRequest{
|
||||
AccessorIDs: []string{tokenAccessor},
|
||||
}
|
||||
s.parseRegion(req, &args.Region)
|
||||
|
||||
var out structs.GenericResponse
|
||||
if err := s.agent.RPC("ACL.DeleteTokens", &args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setIndex(resp, out.Index)
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -173,3 +173,167 @@ func TestHTTP_ACLPolicyDelete(t *testing.T) {
|
|||
assert.Nil(t, out)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_ACLTokenList(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
p1 := mock.ACLToken()
|
||||
p2 := mock.ACLToken()
|
||||
p3 := mock.ACLToken()
|
||||
args := structs.ACLTokenUpsertRequest{
|
||||
Tokens: []*structs.ACLToken{p1, p2, p3},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("GET", "/v1/acl/tokens", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
obj, err := s.Server.ACLTokensRequest(respW, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check for the index
|
||||
if respW.HeaderMap.Get("X-Nomad-Index") == "" {
|
||||
t.Fatalf("missing index")
|
||||
}
|
||||
if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
|
||||
t.Fatalf("missing known leader")
|
||||
}
|
||||
if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
|
||||
t.Fatalf("missing last contact")
|
||||
}
|
||||
|
||||
// Check the output
|
||||
n := obj.([]*structs.ACLTokenListStub)
|
||||
if len(n) != 3 {
|
||||
t.Fatalf("bad: %#v", n)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_ACLTokenQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
p1 := mock.ACLToken()
|
||||
args := structs.ACLTokenUpsertRequest{
|
||||
Tokens: []*structs.ACLToken{p1},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("GET", "/v1/acl/token/"+p1.AccessorID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
obj, err := s.Server.ACLTokenSpecificRequest(respW, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check for the index
|
||||
if respW.HeaderMap.Get("X-Nomad-Index") == "" {
|
||||
t.Fatalf("missing index")
|
||||
}
|
||||
if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
|
||||
t.Fatalf("missing known leader")
|
||||
}
|
||||
if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
|
||||
t.Fatalf("missing last contact")
|
||||
}
|
||||
|
||||
// Check the output
|
||||
n := obj.(*structs.ACLToken)
|
||||
if n.AccessorID != p1.AccessorID {
|
||||
t.Fatalf("bad: %#v", n)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_ACLTokenCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
// Make the HTTP request
|
||||
p1 := mock.ACLToken()
|
||||
buf := encodeReq(p1)
|
||||
req, err := http.NewRequest("PUT", "/v1/acl/token", buf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
obj, err := s.Server.ACLTokenSpecificRequest(respW, req)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, obj)
|
||||
|
||||
// Check for the index
|
||||
if respW.HeaderMap.Get("X-Nomad-Index") == "" {
|
||||
t.Fatalf("missing index")
|
||||
}
|
||||
|
||||
// Check token was created
|
||||
state := s.Agent.server.State()
|
||||
out, err := state.ACLTokenByAccessorID(nil, p1.AccessorID)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, out)
|
||||
|
||||
p1.CreateIndex, p1.ModifyIndex = out.CreateIndex, out.ModifyIndex
|
||||
assert.Equal(t, p1.Name, out.Name)
|
||||
assert.Equal(t, p1, out)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_ACLTokenDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
p1 := mock.ACLToken()
|
||||
args := structs.ACLTokenUpsertRequest{
|
||||
Tokens: []*structs.ACLToken{p1},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("DELETE", "/v1/acl/token/"+p1.AccessorID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
obj, err := s.Server.ACLTokenSpecificRequest(respW, req)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, obj)
|
||||
|
||||
// Check for the index
|
||||
if respW.HeaderMap.Get("X-Nomad-Index") == "" {
|
||||
t.Fatalf("missing index")
|
||||
}
|
||||
|
||||
// Check token was created
|
||||
state := s.Agent.server.State()
|
||||
out, err := state.ACLTokenByAccessorID(nil, p1.AccessorID)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, out)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -151,6 +151,9 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
|
|||
s.mux.HandleFunc("/v1/acl/policies", s.wrap(s.ACLPoliciesRequest))
|
||||
s.mux.HandleFunc("/v1/acl/policy/", s.wrap(s.ACLPolicySpecificRequest))
|
||||
|
||||
s.mux.HandleFunc("/v1/acl/tokens", s.wrap(s.ACLTokensRequest))
|
||||
s.mux.HandleFunc("/v1/acl/token", s.wrap(s.ACLTokenSpecificRequest))
|
||||
|
||||
s.mux.HandleFunc("/v1/client/fs/", s.wrap(s.FsRequest))
|
||||
s.mux.HandleFunc("/v1/client/stats", s.wrap(s.ClientStatsRequest))
|
||||
s.mux.HandleFunc("/v1/client/allocation/", s.wrap(s.ClientAllocRequest))
|
||||
|
|
|
@ -234,7 +234,7 @@ func (a *ACL) ListTokens(args *structs.ACLTokenListRequest, reply *structs.ACLTo
|
|||
break
|
||||
}
|
||||
token := raw.(*structs.ACLToken)
|
||||
reply.Tokens = append(reply.Tokens, token)
|
||||
reply.Tokens = append(reply.Tokens, token.Stub())
|
||||
}
|
||||
|
||||
// Use the last index that affected the token table
|
||||
|
|
|
@ -5434,6 +5434,30 @@ type ACLToken struct {
|
|||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
type ACLTokenListStub struct {
|
||||
AccessorID string
|
||||
Name string
|
||||
Type string
|
||||
Policies []string
|
||||
Global bool
|
||||
CreateTime time.Time
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
func (a *ACLToken) Stub() *ACLTokenListStub {
|
||||
return &ACLTokenListStub{
|
||||
AccessorID: a.AccessorID,
|
||||
Name: a.Name,
|
||||
Type: a.Type,
|
||||
Policies: a.Policies,
|
||||
Global: a.Global,
|
||||
CreateTime: a.CreateTime,
|
||||
CreateIndex: a.CreateIndex,
|
||||
ModifyIndex: a.ModifyIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate is used to sanity check a token
|
||||
func (a *ACLToken) Validate() error {
|
||||
var mErr multierror.Error
|
||||
|
@ -5468,7 +5492,7 @@ type ACLTokenSpecificRequest struct {
|
|||
|
||||
// ACLTokenListResponse is used for a list request
|
||||
type ACLTokenListResponse struct {
|
||||
Tokens []*ACLToken
|
||||
Tokens []*ACLTokenListStub
|
||||
QueryMeta
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue