529 lines
15 KiB
Go
529 lines
15 KiB
Go
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"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, fmt.Sprintf("failed to decode request body: %s", err))
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|