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 }