open-nomad/command/agent/acl_endpoint.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
}