github.com/outbrain/consul@v1.4.5/agent/acl_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/hashicorp/consul/acl"
    12  	"github.com/hashicorp/consul/agent/structs"
    13  )
    14  
    15  // aclCreateResponse is used to wrap the ACL ID
    16  type aclBootstrapResponse struct {
    17  	ID string
    18  	structs.ACLToken
    19  }
    20  
    21  // checkACLDisabled will return a standard response if ACLs are disabled. This
    22  // returns true if they are disabled and we should not continue.
    23  func (s *HTTPServer) checkACLDisabled(resp http.ResponseWriter, req *http.Request) bool {
    24  	if s.agent.delegate.ACLsEnabled() {
    25  		return false
    26  	}
    27  
    28  	resp.WriteHeader(http.StatusUnauthorized)
    29  	fmt.Fprint(resp, "ACL support disabled")
    30  	return true
    31  }
    32  
    33  // ACLBootstrap is used to perform a one-time ACL bootstrap operation on
    34  // a cluster to get the first management token.
    35  func (s *HTTPServer) ACLBootstrap(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    36  	if s.checkACLDisabled(resp, req) {
    37  		return nil, nil
    38  	}
    39  
    40  	args := structs.DCSpecificRequest{
    41  		Datacenter: s.agent.config.Datacenter,
    42  	}
    43  
    44  	legacy := false
    45  	legacyStr := req.URL.Query().Get("legacy")
    46  	if legacyStr != "" {
    47  		legacy, _ = strconv.ParseBool(legacyStr)
    48  	}
    49  
    50  	if legacy && s.agent.delegate.UseLegacyACLs() {
    51  		var out structs.ACL
    52  		err := s.agent.RPC("ACL.Bootstrap", &args, &out)
    53  		if err != nil {
    54  			if strings.Contains(err.Error(), structs.ACLBootstrapNotAllowedErr.Error()) {
    55  				resp.WriteHeader(http.StatusForbidden)
    56  				fmt.Fprint(resp, acl.PermissionDeniedError{Cause: err.Error()}.Error())
    57  				return nil, nil
    58  			} else {
    59  				return nil, err
    60  			}
    61  		}
    62  		return &aclBootstrapResponse{ID: out.ID}, nil
    63  	} else {
    64  		var out structs.ACLToken
    65  		err := s.agent.RPC("ACL.BootstrapTokens", &args, &out)
    66  		if err != nil {
    67  			if strings.Contains(err.Error(), structs.ACLBootstrapNotAllowedErr.Error()) {
    68  				resp.WriteHeader(http.StatusForbidden)
    69  				fmt.Fprint(resp, acl.PermissionDeniedError{Cause: err.Error()}.Error())
    70  				return nil, nil
    71  			} else {
    72  				return nil, err
    73  			}
    74  		}
    75  		return &aclBootstrapResponse{ID: out.SecretID, ACLToken: out}, nil
    76  	}
    77  }
    78  
    79  func (s *HTTPServer) ACLReplicationStatus(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    80  	if s.checkACLDisabled(resp, req) {
    81  		return nil, nil
    82  	}
    83  
    84  	// Note that we do not forward to the ACL DC here. This is a query for
    85  	// any DC that's doing replication.
    86  	args := structs.DCSpecificRequest{}
    87  	s.parseSource(req, &args.Source)
    88  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
    89  		return nil, nil
    90  	}
    91  
    92  	// Make the request.
    93  	var out structs.ACLReplicationStatus
    94  	if err := s.agent.RPC("ACL.ReplicationStatus", &args, &out); err != nil {
    95  		return nil, err
    96  	}
    97  	return out, nil
    98  }
    99  
   100  func (s *HTTPServer) ACLRulesTranslate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   101  	if s.checkACLDisabled(resp, req) {
   102  		return nil, nil
   103  	}
   104  
   105  	var token string
   106  	s.parseToken(req, &token)
   107  	rule, err := s.agent.resolveToken(token)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	// Should this require lesser permissions? Really the only reason to require authorization at all is
   112  	// to prevent external entities from DoS Consul with repeated rule translation requests
   113  	if rule != nil && !rule.ACLRead() {
   114  		return nil, acl.ErrPermissionDenied
   115  	}
   116  
   117  	policyBytes, err := ioutil.ReadAll(req.Body)
   118  	if err != nil {
   119  		return nil, BadRequestError{Reason: fmt.Sprintf("Failed to read body: %v", err)}
   120  	}
   121  
   122  	translated, err := acl.TranslateLegacyRules(policyBytes)
   123  	if err != nil {
   124  		return nil, BadRequestError{Reason: err.Error()}
   125  	}
   126  
   127  	resp.Write(translated)
   128  	return nil, nil
   129  }
   130  
   131  func (s *HTTPServer) ACLRulesTranslateLegacyToken(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   132  	if s.checkACLDisabled(resp, req) {
   133  		return nil, nil
   134  	}
   135  
   136  	tokenID := strings.TrimPrefix(req.URL.Path, "/v1/acl/rules/translate/")
   137  	if tokenID == "" {
   138  		return nil, BadRequestError{Reason: "Missing token ID"}
   139  	}
   140  
   141  	args := structs.ACLTokenGetRequest{
   142  		Datacenter:  s.agent.config.Datacenter,
   143  		TokenID:     tokenID,
   144  		TokenIDType: structs.ACLTokenAccessor,
   145  	}
   146  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   147  		return nil, nil
   148  	}
   149  
   150  	if args.Datacenter == "" {
   151  		args.Datacenter = s.agent.config.Datacenter
   152  	}
   153  
   154  	// Do not allow blocking
   155  	args.QueryOptions.MinQueryIndex = 0
   156  
   157  	var out structs.ACLTokenResponse
   158  	defer setMeta(resp, &out.QueryMeta)
   159  	if err := s.agent.RPC("ACL.TokenRead", &args, &out); err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	if out.Token == nil {
   164  		return nil, acl.ErrNotFound
   165  	}
   166  
   167  	if out.Token.Rules == "" {
   168  		return nil, fmt.Errorf("The specified token does not have any rules set")
   169  	}
   170  
   171  	translated, err := acl.TranslateLegacyRules([]byte(out.Token.Rules))
   172  	if err != nil {
   173  		return nil, fmt.Errorf("Failed to parse legacy rules: %v", err)
   174  	}
   175  
   176  	resp.Write(translated)
   177  	return nil, nil
   178  }
   179  
   180  func (s *HTTPServer) ACLPolicyList(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   181  	if s.checkACLDisabled(resp, req) {
   182  		return nil, nil
   183  	}
   184  
   185  	var args structs.ACLPolicyListRequest
   186  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   187  		return nil, nil
   188  	}
   189  
   190  	if args.Datacenter == "" {
   191  		args.Datacenter = s.agent.config.Datacenter
   192  	}
   193  
   194  	var out structs.ACLPolicyListResponse
   195  	defer setMeta(resp, &out.QueryMeta)
   196  	if err := s.agent.RPC("ACL.PolicyList", &args, &out); err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	// make sure we return an array and not nil
   201  	if out.Policies == nil {
   202  		out.Policies = make(structs.ACLPolicyListStubs, 0)
   203  	}
   204  
   205  	return out.Policies, nil
   206  }
   207  
   208  func (s *HTTPServer) ACLPolicyCRUD(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   209  	if s.checkACLDisabled(resp, req) {
   210  		return nil, nil
   211  	}
   212  
   213  	var fn func(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error)
   214  
   215  	switch req.Method {
   216  	case "GET":
   217  		fn = s.ACLPolicyRead
   218  
   219  	case "PUT":
   220  		fn = s.ACLPolicyWrite
   221  
   222  	case "DELETE":
   223  		fn = s.ACLPolicyDelete
   224  
   225  	default:
   226  		return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
   227  	}
   228  
   229  	policyID := strings.TrimPrefix(req.URL.Path, "/v1/acl/policy/")
   230  	if policyID == "" && req.Method != "PUT" {
   231  		return nil, BadRequestError{Reason: "Missing policy ID"}
   232  	}
   233  
   234  	return fn(resp, req, policyID)
   235  }
   236  
   237  func (s *HTTPServer) ACLPolicyRead(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error) {
   238  	args := structs.ACLPolicyGetRequest{
   239  		Datacenter: s.agent.config.Datacenter,
   240  		PolicyID:   policyID,
   241  	}
   242  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   243  		return nil, nil
   244  	}
   245  
   246  	if args.Datacenter == "" {
   247  		args.Datacenter = s.agent.config.Datacenter
   248  	}
   249  
   250  	var out structs.ACLPolicyResponse
   251  	defer setMeta(resp, &out.QueryMeta)
   252  	if err := s.agent.RPC("ACL.PolicyRead", &args, &out); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	if out.Policy == nil {
   257  		return nil, acl.ErrNotFound
   258  	}
   259  
   260  	return out.Policy, nil
   261  }
   262  
   263  func (s *HTTPServer) ACLPolicyCreate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   264  	if s.checkACLDisabled(resp, req) {
   265  		return nil, nil
   266  	}
   267  
   268  	return s.ACLPolicyWrite(resp, req, "")
   269  }
   270  
   271  // fixCreateTimeAndHash is used to help in decoding the CreateTime and Hash
   272  // attributes from the ACL Token/Policy create/update requests. It is needed
   273  // to help mapstructure decode things properly when decodeBody is used.
   274  func fixCreateTimeAndHash(raw interface{}) error {
   275  	rawMap, ok := raw.(map[string]interface{})
   276  	if !ok {
   277  		return nil
   278  	}
   279  
   280  	if val, ok := rawMap["CreateTime"]; ok {
   281  		if sval, ok := val.(string); ok {
   282  			t, err := time.Parse(time.RFC3339, sval)
   283  			if err != nil {
   284  				return err
   285  			}
   286  			rawMap["CreateTime"] = t
   287  		}
   288  	}
   289  
   290  	if val, ok := rawMap["Hash"]; ok {
   291  		if sval, ok := val.(string); ok {
   292  			rawMap["Hash"] = []byte(sval)
   293  		}
   294  	}
   295  	return nil
   296  }
   297  
   298  func (s *HTTPServer) ACLPolicyWrite(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error) {
   299  	args := structs.ACLPolicySetRequest{
   300  		Datacenter: s.agent.config.Datacenter,
   301  	}
   302  	s.parseToken(req, &args.Token)
   303  
   304  	if err := decodeBody(req, &args.Policy, fixCreateTimeAndHash); err != nil {
   305  		return nil, BadRequestError{Reason: fmt.Sprintf("Policy decoding failed: %v", err)}
   306  	}
   307  
   308  	args.Policy.Syntax = acl.SyntaxCurrent
   309  
   310  	if args.Policy.ID != "" && args.Policy.ID != policyID {
   311  		return nil, BadRequestError{Reason: "Policy ID in URL and payload do not match"}
   312  	} else if args.Policy.ID == "" {
   313  		args.Policy.ID = policyID
   314  	}
   315  
   316  	var out structs.ACLPolicy
   317  	if err := s.agent.RPC("ACL.PolicySet", args, &out); err != nil {
   318  		return nil, err
   319  	}
   320  
   321  	return &out, nil
   322  }
   323  
   324  func (s *HTTPServer) ACLPolicyDelete(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error) {
   325  	args := structs.ACLPolicyDeleteRequest{
   326  		Datacenter: s.agent.config.Datacenter,
   327  		PolicyID:   policyID,
   328  	}
   329  	s.parseToken(req, &args.Token)
   330  
   331  	var ignored string
   332  	if err := s.agent.RPC("ACL.PolicyDelete", args, &ignored); err != nil {
   333  		return nil, err
   334  	}
   335  
   336  	return true, nil
   337  }
   338  
   339  func (s *HTTPServer) ACLTokenList(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   340  	if s.checkACLDisabled(resp, req) {
   341  		return nil, nil
   342  	}
   343  
   344  	args := &structs.ACLTokenListRequest{
   345  		IncludeLocal:  true,
   346  		IncludeGlobal: true,
   347  	}
   348  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   349  		return nil, nil
   350  	}
   351  
   352  	if args.Datacenter == "" {
   353  		args.Datacenter = s.agent.config.Datacenter
   354  	}
   355  
   356  	args.Policy = req.URL.Query().Get("policy")
   357  
   358  	var out structs.ACLTokenListResponse
   359  	defer setMeta(resp, &out.QueryMeta)
   360  	if err := s.agent.RPC("ACL.TokenList", &args, &out); err != nil {
   361  		return nil, err
   362  	}
   363  
   364  	return out.Tokens, nil
   365  }
   366  
   367  func (s *HTTPServer) ACLTokenCRUD(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   368  	if s.checkACLDisabled(resp, req) {
   369  		return nil, nil
   370  	}
   371  
   372  	var fn func(resp http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error)
   373  
   374  	switch req.Method {
   375  	case "GET":
   376  		fn = s.ACLTokenGet
   377  
   378  	case "PUT":
   379  		fn = s.ACLTokenSet
   380  
   381  	case "DELETE":
   382  		fn = s.ACLTokenDelete
   383  
   384  	default:
   385  		return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
   386  	}
   387  
   388  	tokenID := strings.TrimPrefix(req.URL.Path, "/v1/acl/token/")
   389  	if strings.HasSuffix(tokenID, "/clone") && req.Method == "PUT" {
   390  		tokenID = tokenID[:len(tokenID)-6]
   391  		fn = s.ACLTokenClone
   392  	}
   393  	if tokenID == "" && req.Method != "PUT" {
   394  		return nil, BadRequestError{Reason: "Missing token ID"}
   395  	}
   396  
   397  	return fn(resp, req, tokenID)
   398  }
   399  
   400  func (s *HTTPServer) ACLTokenSelf(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   401  	if s.checkACLDisabled(resp, req) {
   402  		return nil, nil
   403  	}
   404  
   405  	args := structs.ACLTokenGetRequest{
   406  		TokenIDType: structs.ACLTokenSecret,
   407  	}
   408  
   409  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   410  		return nil, nil
   411  	}
   412  
   413  	// copy the token parameter to the ID
   414  	args.TokenID = args.Token
   415  
   416  	if args.Datacenter == "" {
   417  		args.Datacenter = s.agent.config.Datacenter
   418  	}
   419  
   420  	var out structs.ACLTokenResponse
   421  	defer setMeta(resp, &out.QueryMeta)
   422  	if err := s.agent.RPC("ACL.TokenRead", &args, &out); err != nil {
   423  		return nil, err
   424  	}
   425  
   426  	if out.Token == nil {
   427  		return nil, acl.ErrNotFound
   428  	}
   429  
   430  	return out.Token, nil
   431  }
   432  
   433  func (s *HTTPServer) ACLTokenCreate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   434  	if s.checkACLDisabled(resp, req) {
   435  		return nil, nil
   436  	}
   437  
   438  	return s.ACLTokenSet(resp, req, "")
   439  }
   440  
   441  func (s *HTTPServer) ACLTokenGet(resp http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error) {
   442  	args := structs.ACLTokenGetRequest{
   443  		Datacenter:  s.agent.config.Datacenter,
   444  		TokenID:     tokenID,
   445  		TokenIDType: structs.ACLTokenAccessor,
   446  	}
   447  
   448  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   449  		return nil, nil
   450  	}
   451  
   452  	if args.Datacenter == "" {
   453  		args.Datacenter = s.agent.config.Datacenter
   454  	}
   455  
   456  	var out structs.ACLTokenResponse
   457  	defer setMeta(resp, &out.QueryMeta)
   458  	if err := s.agent.RPC("ACL.TokenRead", &args, &out); err != nil {
   459  		return nil, err
   460  	}
   461  
   462  	if out.Token == nil {
   463  		return nil, acl.ErrNotFound
   464  	}
   465  
   466  	return out.Token, nil
   467  }
   468  
   469  func (s *HTTPServer) ACLTokenSet(resp http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error) {
   470  	args := structs.ACLTokenSetRequest{
   471  		Datacenter: s.agent.config.Datacenter,
   472  	}
   473  	s.parseToken(req, &args.Token)
   474  
   475  	if err := decodeBody(req, &args.ACLToken, fixCreateTimeAndHash); err != nil {
   476  		return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
   477  	}
   478  
   479  	if args.ACLToken.AccessorID != "" && args.ACLToken.AccessorID != tokenID {
   480  		return nil, BadRequestError{Reason: "Token Accessor ID in URL and payload do not match"}
   481  	} else if args.ACLToken.AccessorID == "" {
   482  		args.ACLToken.AccessorID = tokenID
   483  	}
   484  
   485  	var out structs.ACLToken
   486  	if err := s.agent.RPC("ACL.TokenSet", args, &out); err != nil {
   487  		return nil, err
   488  	}
   489  
   490  	return &out, nil
   491  }
   492  
   493  func (s *HTTPServer) ACLTokenDelete(resp http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error) {
   494  	args := structs.ACLTokenDeleteRequest{
   495  		Datacenter: s.agent.config.Datacenter,
   496  		TokenID:    tokenID,
   497  	}
   498  	s.parseToken(req, &args.Token)
   499  
   500  	var ignored string
   501  	if err := s.agent.RPC("ACL.TokenDelete", args, &ignored); err != nil {
   502  		return nil, err
   503  	}
   504  	return true, nil
   505  }
   506  
   507  func (s *HTTPServer) ACLTokenClone(resp http.ResponseWriter, req *http.Request, tokenID string) (interface{}, error) {
   508  	if s.checkACLDisabled(resp, req) {
   509  		return nil, nil
   510  	}
   511  
   512  	args := structs.ACLTokenSetRequest{
   513  		Datacenter: s.agent.config.Datacenter,
   514  	}
   515  
   516  	if err := decodeBody(req, &args.ACLToken, fixCreateTimeAndHash); err != nil && err.Error() != "EOF" {
   517  		return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
   518  	}
   519  	s.parseToken(req, &args.Token)
   520  
   521  	// Set this for the ID to clone
   522  	args.ACLToken.AccessorID = tokenID
   523  
   524  	var out structs.ACLToken
   525  	if err := s.agent.RPC("ACL.TokenClone", args, &out); err != nil {
   526  		return nil, err
   527  	}
   528  
   529  	return &out, nil
   530  }