2736cf0dfa
Making the ACL Role listing return object a stub future-proofs the endpoint. In the event the role object grows, we are not bound by having to return all fields within the list endpoint or change the signature of the endpoint to reduce the list return size.
528 lines
15 KiB
Go
528 lines
15 KiB
Go
package agent
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
func (s *HTTPServer) ACLPoliciesRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
if req.Method != "GET" {
|
|
return nil, CodedError(405, ErrInvalidMethod)
|
|
}
|
|
|
|
args := structs.ACLPolicyListRequest{}
|
|
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
|
return nil, nil
|
|
}
|
|
|
|
var out structs.ACLPolicyListResponse
|
|
if err := s.agent.RPC("ACL.ListPolicies", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setMeta(resp, &out.QueryMeta)
|
|
if out.Policies == nil {
|
|
out.Policies = make([]*structs.ACLPolicyListStub, 0)
|
|
}
|
|
return out.Policies, nil
|
|
}
|
|
|
|
func (s *HTTPServer) ACLPolicySpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
name := strings.TrimPrefix(req.URL.Path, "/v1/acl/policy/")
|
|
if len(name) == 0 {
|
|
return nil, CodedError(400, "Missing Policy Name")
|
|
}
|
|
switch req.Method {
|
|
case "GET":
|
|
return s.aclPolicyQuery(resp, req, name)
|
|
case "PUT", "POST":
|
|
return s.aclPolicyUpdate(resp, req, name)
|
|
case "DELETE":
|
|
return s.aclPolicyDelete(resp, req, name)
|
|
default:
|
|
return nil, CodedError(405, ErrInvalidMethod)
|
|
}
|
|
}
|
|
|
|
func (s *HTTPServer) aclPolicyQuery(resp http.ResponseWriter, req *http.Request,
|
|
policyName string) (interface{}, error) {
|
|
args := structs.ACLPolicySpecificRequest{
|
|
Name: policyName,
|
|
}
|
|
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
|
return nil, nil
|
|
}
|
|
|
|
var out structs.SingleACLPolicyResponse
|
|
if err := s.agent.RPC("ACL.GetPolicy", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setMeta(resp, &out.QueryMeta)
|
|
if out.Policy == nil {
|
|
return nil, CodedError(404, "ACL policy not found")
|
|
}
|
|
return out.Policy, nil
|
|
}
|
|
|
|
func (s *HTTPServer) aclPolicyUpdate(resp http.ResponseWriter, req *http.Request,
|
|
policyName string) (interface{}, error) {
|
|
// Parse the policy
|
|
var policy structs.ACLPolicy
|
|
if err := decodeBody(req, &policy); err != nil {
|
|
return nil, CodedError(500, err.Error())
|
|
}
|
|
|
|
// Ensure the policy name matches
|
|
if policy.Name != policyName {
|
|
return nil, CodedError(400, "ACL policy name does not match request path")
|
|
}
|
|
|
|
// Format the request
|
|
args := structs.ACLPolicyUpsertRequest{
|
|
Policies: []*structs.ACLPolicy{&policy},
|
|
}
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var out structs.GenericResponse
|
|
if err := s.agent.RPC("ACL.UpsertPolicies", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
setIndex(resp, out.Index)
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *HTTPServer) aclPolicyDelete(resp http.ResponseWriter, req *http.Request,
|
|
policyName string) (interface{}, error) {
|
|
|
|
args := structs.ACLPolicyDeleteRequest{
|
|
Names: []string{policyName},
|
|
}
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var out structs.GenericResponse
|
|
if err := s.agent.RPC("ACL.DeletePolicies", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
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) ACLTokenBootstrap(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
// Ensure this is a PUT or POST
|
|
if !(req.Method == "PUT" || req.Method == "POST") {
|
|
return nil, CodedError(405, ErrInvalidMethod)
|
|
}
|
|
|
|
var args structs.ACLTokenBootstrapRequest
|
|
|
|
if req.ContentLength != 0 {
|
|
if err := decodeBody(req, &args); err != nil {
|
|
return nil, CodedError(400, err.Error())
|
|
}
|
|
}
|
|
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var out structs.ACLTokenUpsertResponse
|
|
if err := s.agent.RPC("ACL.Bootstrap", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
setIndex(resp, out.Index)
|
|
if len(out.Tokens) > 0 {
|
|
return out.Tokens[0], nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *HTTPServer) ACLTokenSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
path := req.URL.Path
|
|
|
|
switch path {
|
|
case "/v1/acl/token":
|
|
if !(req.Method == "PUT" || req.Method == "POST") {
|
|
return nil, CodedError(405, ErrInvalidMethod)
|
|
}
|
|
return s.aclTokenUpdate(resp, req, "")
|
|
case "/v1/acl/token/self":
|
|
return s.aclTokenSelf(resp, req)
|
|
}
|
|
|
|
accessor := strings.TrimPrefix(path, "/v1/acl/token/")
|
|
return s.aclTokenCrud(resp, req, accessor)
|
|
}
|
|
|
|
func (s *HTTPServer) aclTokenCrud(resp http.ResponseWriter, req *http.Request,
|
|
tokenAccessor string) (interface{}, error) {
|
|
if tokenAccessor == "" {
|
|
return nil, CodedError(400, "Missing Token Accessor")
|
|
}
|
|
|
|
switch req.Method {
|
|
case "GET":
|
|
return s.aclTokenQuery(resp, req, tokenAccessor)
|
|
case "PUT", "POST":
|
|
return s.aclTokenUpdate(resp, req, tokenAccessor)
|
|
case "DELETE":
|
|
return s.aclTokenDelete(resp, req, tokenAccessor)
|
|
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) aclTokenSelf(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
if req.Method != "GET" {
|
|
return nil, CodedError(405, ErrInvalidMethod)
|
|
}
|
|
args := structs.ResolveACLTokenRequest{}
|
|
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
|
return nil, nil
|
|
}
|
|
|
|
args.SecretID = args.AuthToken
|
|
|
|
var out structs.ResolveACLTokenResponse
|
|
if err := s.agent.RPC("ACL.ResolveToken", &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.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var out structs.ACLTokenUpsertResponse
|
|
if err := s.agent.RPC("ACL.UpsertTokens", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
setIndex(resp, out.Index)
|
|
if len(out.Tokens) > 0 {
|
|
return out.Tokens[0], nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *HTTPServer) aclTokenDelete(resp http.ResponseWriter, req *http.Request,
|
|
tokenAccessor string) (interface{}, error) {
|
|
|
|
args := structs.ACLTokenDeleteRequest{
|
|
AccessorIDs: []string{tokenAccessor},
|
|
}
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
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
|
|
}
|
|
|
|
func (s *HTTPServer) UpsertOneTimeToken(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
// Ensure this is a PUT or POST
|
|
if !(req.Method == "PUT" || req.Method == "POST") {
|
|
return nil, CodedError(405, ErrInvalidMethod)
|
|
}
|
|
|
|
// the request body is empty but we need to parse to get the auth token
|
|
args := structs.OneTimeTokenUpsertRequest{}
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var out structs.OneTimeTokenUpsertResponse
|
|
if err := s.agent.RPC("ACL.UpsertOneTimeToken", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
setIndex(resp, out.Index)
|
|
return out, nil
|
|
}
|
|
|
|
func (s *HTTPServer) ExchangeOneTimeToken(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
// Ensure this is a PUT or POST
|
|
if !(req.Method == "PUT" || req.Method == "POST") {
|
|
return nil, CodedError(405, ErrInvalidMethod)
|
|
}
|
|
|
|
var args structs.OneTimeTokenExchangeRequest
|
|
if err := decodeBody(req, &args); err != nil {
|
|
return nil, CodedError(500, err.Error())
|
|
}
|
|
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var out structs.OneTimeTokenExchangeResponse
|
|
if err := s.agent.RPC("ACL.ExchangeOneTimeToken", &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
setIndex(resp, out.Index)
|
|
return out, nil
|
|
}
|
|
|
|
// ACLRoleListRequest performs a listing of ACL roles and is callable via the
|
|
// /v1/acl/roles HTTP API.
|
|
func (s *HTTPServer) ACLRoleListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
// The endpoint only supports GET requests.
|
|
if req.Method != http.MethodGet {
|
|
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
|
}
|
|
|
|
// Set up the request args and parse this to ensure the query options are
|
|
// set.
|
|
args := structs.ACLRolesListRequest{}
|
|
|
|
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Perform the RPC request.
|
|
var reply structs.ACLRolesListResponse
|
|
if err := s.agent.RPC(structs.ACLListRolesRPCMethod, &args, &reply); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setMeta(resp, &reply.QueryMeta)
|
|
|
|
if reply.ACLRoles == nil {
|
|
reply.ACLRoles = make([]*structs.ACLRoleListStub, 0)
|
|
}
|
|
return reply.ACLRoles, nil
|
|
}
|
|
|
|
// ACLRoleRequest creates a new ACL role and is callable via the
|
|
// /v1/acl/role HTTP API.
|
|
func (s *HTTPServer) ACLRoleRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
// // The endpoint only supports PUT or POST requests.
|
|
if !(req.Method == http.MethodPut || req.Method == http.MethodPost) {
|
|
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
|
}
|
|
|
|
// Use the generic upsert function without setting an ID as this will be
|
|
// handled by the Nomad leader.
|
|
return s.aclRoleUpsertRequest(resp, req, "")
|
|
}
|
|
|
|
// ACLRoleSpecificRequest is callable via the /v1/acl/role/ HTTP API and
|
|
// handles read via both the role name and ID, updates, and deletions.
|
|
func (s *HTTPServer) ACLRoleSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
// Grab the suffix of the request, so we can further understand it.
|
|
reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/acl/role/")
|
|
|
|
// Split the request suffix in order to identify whether this is a lookup
|
|
// of a service, or whether this includes a service and service identifier.
|
|
suffixParts := strings.Split(reqSuffix, "/")
|
|
|
|
switch len(suffixParts) {
|
|
case 1:
|
|
// Ensure the role ID is not an empty string which is possible if the
|
|
// caller requested "/v1/acl/role/"
|
|
if suffixParts[0] == "" {
|
|
return nil, CodedError(http.StatusBadRequest, "missing ACL role ID")
|
|
}
|
|
return s.aclRoleRequest(resp, req, suffixParts[0])
|
|
case 2:
|
|
// This endpoint only supports GET.
|
|
if req.Method != http.MethodGet {
|
|
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
|
}
|
|
|
|
// Ensure that the path is correct, otherwise the call could use
|
|
// "/v1/acl/role/foobar/role-name" and successfully pass through here.
|
|
if suffixParts[0] != "name" {
|
|
return nil, CodedError(http.StatusBadRequest, "invalid URI")
|
|
}
|
|
|
|
// Ensure the role name is not an empty string which is possible if the
|
|
// caller requested "/v1/acl/role/name/"
|
|
if suffixParts[1] == "" {
|
|
return nil, CodedError(http.StatusBadRequest, "missing ACL role name")
|
|
}
|
|
|
|
return s.aclRoleGetByNameRequest(resp, req, suffixParts[1])
|
|
|
|
default:
|
|
return nil, CodedError(http.StatusBadRequest, "invalid URI")
|
|
}
|
|
}
|
|
|
|
func (s *HTTPServer) aclRoleRequest(
|
|
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
|
|
|
// Identify the method which indicates which downstream function should be
|
|
// called.
|
|
switch req.Method {
|
|
case http.MethodGet:
|
|
return s.aclRoleGetByIDRequest(resp, req, roleID)
|
|
case http.MethodDelete:
|
|
return s.aclRoleDeleteRequest(resp, req, roleID)
|
|
case http.MethodPost, http.MethodPut:
|
|
return s.aclRoleUpsertRequest(resp, req, roleID)
|
|
default:
|
|
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
|
}
|
|
}
|
|
|
|
func (s *HTTPServer) aclRoleGetByIDRequest(
|
|
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
|
|
|
args := structs.ACLRoleByIDRequest{
|
|
RoleID: roleID,
|
|
}
|
|
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
|
return nil, nil
|
|
}
|
|
|
|
var reply structs.ACLRoleByIDResponse
|
|
if err := s.agent.RPC(structs.ACLGetRoleByIDRPCMethod, &args, &reply); err != nil {
|
|
return nil, err
|
|
}
|
|
setMeta(resp, &reply.QueryMeta)
|
|
|
|
if reply.ACLRole == nil {
|
|
return nil, CodedError(http.StatusNotFound, "ACL role not found")
|
|
}
|
|
return reply.ACLRole, nil
|
|
}
|
|
|
|
func (s *HTTPServer) aclRoleDeleteRequest(
|
|
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
|
|
|
args := structs.ACLRolesDeleteByIDRequest{
|
|
ACLRoleIDs: []string{roleID},
|
|
}
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var reply structs.ACLRolesDeleteByIDResponse
|
|
if err := s.agent.RPC(structs.ACLDeleteRolesByIDRPCMethod, &args, &reply); err != nil {
|
|
return nil, err
|
|
}
|
|
setIndex(resp, reply.Index)
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
// aclRoleUpsertRequest handles upserting an ACL to the Nomad servers. It can
|
|
// handle both new creations, and updates to existing roles.
|
|
func (s *HTTPServer) aclRoleUpsertRequest(
|
|
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
|
|
|
// Decode the ACL role.
|
|
var aclRole structs.ACLRole
|
|
if err := decodeBody(req, &aclRole); err != nil {
|
|
return nil, CodedError(http.StatusInternalServerError, err.Error())
|
|
}
|
|
|
|
// Ensure the request path ID matches the ACL role ID that was decoded.
|
|
// Only perform this check on updates as a generic error on creation might
|
|
// be confusing to operators as there is no specific role request path.
|
|
if roleID != "" && roleID != aclRole.ID {
|
|
return nil, CodedError(http.StatusBadRequest, "ACL role ID does not match request path")
|
|
}
|
|
|
|
args := structs.ACLRolesUpsertRequest{
|
|
ACLRoles: []*structs.ACLRole{&aclRole},
|
|
}
|
|
s.parseWriteRequest(req, &args.WriteRequest)
|
|
|
|
var out structs.ACLRolesUpsertResponse
|
|
if err := s.agent.RPC(structs.ACLUpsertRolesRPCMethod, &args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
setIndex(resp, out.Index)
|
|
|
|
if len(out.ACLRoles) > 0 {
|
|
return out.ACLRoles[0], nil
|
|
}
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
func (s *HTTPServer) aclRoleGetByNameRequest(
|
|
resp http.ResponseWriter, req *http.Request, roleName string) (interface{}, error) {
|
|
|
|
args := structs.ACLRoleByNameRequest{
|
|
RoleName: roleName,
|
|
}
|
|
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
|
return nil, nil
|
|
}
|
|
|
|
var reply structs.ACLRoleByNameResponse
|
|
if err := s.agent.RPC(structs.ACLGetRoleByNameRPCMethod, &args, &reply); err != nil {
|
|
return nil, err
|
|
}
|
|
setMeta(resp, &reply.QueryMeta)
|
|
|
|
if reply.ACLRole == nil {
|
|
return nil, CodedError(http.StatusNotFound, "ACL role not found")
|
|
}
|
|
return reply.ACLRole, nil
|
|
}
|