Skip to content
Permalink
Browse files

Add Namespace support to the API module and the CLI commands (#6874)

Also update the Docs and fixup the HTTP API to return proper errors when someone attempts to use Namespaces with an OSS agent.

Add Namespace HTTP API docs

Make all API endpoints disallow unknown fields
  • Loading branch information
mkeeler committed Dec 6, 2019
1 parent deb91f3 commit a704ebe6392a645f64ce56e87aec30bf1ac3df29
Showing with 1,648 additions and 82 deletions.
  1. +75 −30 agent/acl_endpoint.go
  2. +1 −1 agent/agent_endpoint_test.go
  3. +1 −2 agent/discovery_chain_endpoint.go
  4. +2 −5 agent/http.go
  5. +29 −1 agent/http_oss.go
  6. +3 −1 agent/kvs_endpoint.go
  7. +20 −7 agent/session_endpoint.go
  8. +7 −4 agent/structs/acl.go
  9. +2 −2 agent/structs/check_definition.go
  10. +7 −2 agent/structs/check_type.go
  11. +3 −2 agent/structs/config_entry_discoverychain.go
  12. +1 −2 agent/structs/connect_ca.go
  13. +3 −3 agent/structs/connect_proxy_config.go
  14. +3 −1 agent/structs/discovery_chain.go
  15. +2 −2 agent/structs/intention.go
  16. +2 −3 agent/structs/service_definition.go
  17. +5 −3 agent/structs/structs.go
  18. +32 −0 api/acl.go
  19. +30 −0 command/acl/acl_helpers.go
  20. +1 −0 command/acl/authmethod/create/authmethod_create.go
  21. +1 −0 command/acl/authmethod/delete/authmethod_delete.go
  22. +1 −0 command/acl/authmethod/list/authmethod_list.go
  23. +1 −0 command/acl/authmethod/read/authmethod_read.go
  24. +1 −0 command/acl/authmethod/update/authmethod_update.go
  25. +1 −0 command/acl/bindingrule/create/bindingrule_create.go
  26. +1 −0 command/acl/bindingrule/delete/bindingrule_delete.go
  27. +1 −0 command/acl/bindingrule/list/bindingrule_list.go
  28. +1 −0 command/acl/bindingrule/read/bindingrule_read.go
  29. +1 −0 command/acl/bindingrule/update/bindingrule_update.go
  30. +1 −0 command/acl/policy/create/policy_create.go
  31. +1 −0 command/acl/policy/delete/policy_delete.go
  32. +1 −0 command/acl/policy/list/policy_list.go
  33. +1 −0 command/acl/policy/read/policy_read.go
  34. +1 −0 command/acl/policy/update/policy_update.go
  35. +1 −0 command/acl/role/create/role_create.go
  36. +1 −0 command/acl/role/delete/role_delete.go
  37. +1 −0 command/acl/role/list/role_list.go
  38. +1 −0 command/acl/role/read/role_read.go
  39. +1 −0 command/acl/role/update/role_update.go
  40. +1 −0 command/acl/token/clone/token_clone.go
  41. +1 −0 command/acl/token/clone/token_clone_test.go
  42. +1 −0 command/acl/token/create/token_create.go
  43. +1 −0 command/acl/token/delete/token_delete.go
  44. +1 −0 command/acl/token/list/token_list.go
  45. +1 −0 command/acl/token/read/token_read.go
  46. +1 −0 command/acl/token/update/token_update.go
  47. +13 −6 command/flags/http.go
  48. +1 −0 command/kv/del/kv_delete.go
  49. +1 −0 command/kv/exp/kv_export.go
  50. +1 −0 command/kv/get/kv_get.go
  51. +1 −0 command/kv/imp/kv_import.go
  52. +1 −0 command/kv/put/kv_put.go
  53. +1 −0 command/login/login.go
  54. +1 −0 command/logout/logout.go
  55. +27 −0 lib/json.go
  56. +6 −0 website/source/api/acl/acl.html.md
  57. +35 −1 website/source/api/acl/auth-methods.html.md
  58. +32 −0 website/source/api/acl/binding-rules.html.md
  59. +33 −0 website/source/api/acl/policies.html.md
  60. +39 −0 website/source/api/acl/roles.html.md
  61. +36 −0 website/source/api/acl/tokens.html.md
  62. +483 −0 website/source/api/namespaces.html.md
  63. +64 −0 website/source/docs/acl/{acl-rules.html.md → acl-rules.html.md.erb}
  64. +20 −1 website/source/docs/acl/acl-system.html.md
  65. +3 −0 website/source/docs/commands/_http_api_namespace_options.html.md
  66. +4 −0 website/source/docs/commands/acl/auth-method/create.html.md.erb
  67. +4 −0 website/source/docs/commands/acl/auth-method/delete.html.md.erb
  68. +4 −0 website/source/docs/commands/acl/auth-method/list.html.md.erb
  69. +4 −0 website/source/docs/commands/acl/auth-method/read.html.md.erb
  70. +4 −0 website/source/docs/commands/acl/auth-method/update.html.md.erb
  71. +4 −0 website/source/docs/commands/acl/binding-rule/create.html.md.erb
  72. +4 −0 website/source/docs/commands/acl/binding-rule/delete.html.md.erb
  73. +4 −0 website/source/docs/commands/acl/binding-rule/list.html.md.erb
  74. +4 −0 website/source/docs/commands/acl/binding-rule/read.html.md.erb
  75. +4 −0 website/source/docs/commands/acl/binding-rule/update.html.md.erb
  76. +4 −0 website/source/docs/commands/acl/policy/create.html.md.erb
  77. +4 −0 website/source/docs/commands/acl/policy/delete.html.md.erb
  78. +4 −0 website/source/docs/commands/acl/policy/list.html.md.erb
  79. +4 −0 website/source/docs/commands/acl/policy/read.html.md.erb
  80. +4 −0 website/source/docs/commands/acl/policy/update.html.md.erb
  81. +4 −0 website/source/docs/commands/acl/role/create.html.md.erb
  82. +4 −0 website/source/docs/commands/acl/role/delete.html.md.erb
  83. +4 −0 website/source/docs/commands/acl/role/list.html.md.erb
  84. +4 −0 website/source/docs/commands/acl/role/read.html.md.erb
  85. +4 −0 website/source/docs/commands/acl/role/update.html.md.erb
  86. +4 −0 website/source/docs/commands/acl/token/clone.html.md.erb
  87. +4 −0 website/source/docs/commands/acl/token/create.html.md.erb
  88. +4 −0 website/source/docs/commands/acl/token/delete.html.md.erb
  89. +4 −0 website/source/docs/commands/acl/token/list.html.md.erb
  90. +4 −0 website/source/docs/commands/acl/token/read.html.md.erb
  91. +4 −0 website/source/docs/commands/acl/token/update.html.md.erb
  92. +4 −0 website/source/docs/commands/kv/delete.html.markdown.erb
  93. +4 −0 website/source/docs/commands/kv/export.html.markdown.erb
  94. +4 −0 website/source/docs/commands/kv/get.html.markdown.erb
  95. +4 −2 website/source/docs/commands/kv/import.html.markdown.erb
  96. +4 −0 website/source/docs/commands/kv/put.html.markdown.erb
  97. +71 −0 website/source/docs/commands/namespace.html.md.erb
  98. +31 −0 website/source/docs/commands/namespace/delete.html.md.erb
  99. +98 −0 website/source/docs/commands/namespace/list.html.md.erb
  100. +66 −0 website/source/docs/commands/namespace/read.html.md.erb
  101. +75 −0 website/source/docs/commands/namespace/write.html.md.erb
  102. +1 −0 website/source/docs/enterprise/index.html.md
  103. +104 −0 website/source/docs/enterprise/namespaces/index.html.md
  104. +3 −0 website/source/layouts/api.erb
  105. +22 −1 website/source/layouts/docs.erb
@@ -9,6 +9,7 @@ import (

"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/lib"
)

// aclCreateResponse is used to wrap the ACL ID
@@ -186,7 +187,9 @@ func (s *HTTPServer) ACLPolicyList(resp http.ResponseWriter, req *http.Request)
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -244,7 +247,9 @@ func (s *HTTPServer) ACLPolicyRead(resp http.ResponseWriter, req *http.Request,
return nil, nil
}

s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -281,9 +286,11 @@ func (s *HTTPServer) aclPolicyWriteInternal(resp http.ResponseWriter, req *http.
Datacenter: s.agent.config.Datacenter,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.Policy.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.Policy.EnterpriseMeta); err != nil {
return nil, err
}

if err := decodeBody(req.Body, &args.Policy); err != nil {
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.Policy)); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Policy decoding failed: %v", err)}
}

@@ -315,7 +322,9 @@ func (s *HTTPServer) ACLPolicyDelete(resp http.ResponseWriter, req *http.Request
PolicyID: policyID,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

var ignored string
if err := s.agent.RPC("ACL.PolicyDelete", args, &ignored); err != nil {
@@ -338,7 +347,9 @@ func (s *HTTPServer) ACLTokenList(resp http.ResponseWriter, req *http.Request) (
return nil, nil
}

s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -442,7 +453,9 @@ func (s *HTTPServer) ACLTokenGet(resp http.ResponseWriter, req *http.Request, to
return nil, nil
}

s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -471,9 +484,11 @@ func (s *HTTPServer) aclTokenSetInternal(resp http.ResponseWriter, req *http.Req
Create: create,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta); err != nil {
return nil, err
}

if err := decodeBody(req.Body, &args.ACLToken); err != nil {
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.ACLToken)); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
}

@@ -499,7 +514,9 @@ func (s *HTTPServer) ACLTokenDelete(resp http.ResponseWriter, req *http.Request,
TokenID: tokenID,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

var ignored string
if err := s.agent.RPC("ACL.TokenDelete", args, &ignored); err != nil {
@@ -518,8 +535,10 @@ func (s *HTTPServer) ACLTokenClone(resp http.ResponseWriter, req *http.Request,
Create: true,
}

s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta)
if err := decodeBody(req.Body, &args.ACLToken); err != nil {
if err := s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta); err != nil {
return nil, err
}
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.ACLToken)); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
}
s.parseToken(req, &args.Token)
@@ -544,7 +563,9 @@ func (s *HTTPServer) ACLRoleList(resp http.ResponseWriter, req *http.Request) (i
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -621,7 +642,9 @@ func (s *HTTPServer) ACLRoleRead(resp http.ResponseWriter, req *http.Request, ro
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -654,9 +677,11 @@ func (s *HTTPServer) ACLRoleWrite(resp http.ResponseWriter, req *http.Request, r
Datacenter: s.agent.config.Datacenter,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.Role.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.Role.EnterpriseMeta); err != nil {
return nil, err
}

if err := decodeBody(req.Body, &args.Role); err != nil {
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.Role)); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Role decoding failed: %v", err)}
}

@@ -680,7 +705,9 @@ func (s *HTTPServer) ACLRoleDelete(resp http.ResponseWriter, req *http.Request,
RoleID: roleID,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

var ignored string
if err := s.agent.RPC("ACL.RoleDelete", args, &ignored); err != nil {
@@ -700,7 +727,9 @@ func (s *HTTPServer) ACLBindingRuleList(resp http.ResponseWriter, req *http.Requ
return nil, nil
}

s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -760,7 +789,9 @@ func (s *HTTPServer) ACLBindingRuleRead(resp http.ResponseWriter, req *http.Requ
return nil, nil
}

s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -793,9 +824,11 @@ func (s *HTTPServer) ACLBindingRuleWrite(resp http.ResponseWriter, req *http.Req
Datacenter: s.agent.config.Datacenter,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.BindingRule.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.BindingRule.EnterpriseMeta); err != nil {
return nil, err
}

if err := decodeBody(req.Body, &args.BindingRule); err != nil {
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.BindingRule)); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("BindingRule decoding failed: %v", err)}
}

@@ -819,7 +852,9 @@ func (s *HTTPServer) ACLBindingRuleDelete(resp http.ResponseWriter, req *http.Re
BindingRuleID: bindingRuleID,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

var ignored bool
if err := s.agent.RPC("ACL.BindingRuleDelete", args, &ignored); err != nil {
@@ -838,7 +873,9 @@ func (s *HTTPServer) ACLAuthMethodList(resp http.ResponseWriter, req *http.Reque
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -895,7 +932,9 @@ func (s *HTTPServer) ACLAuthMethodRead(resp http.ResponseWriter, req *http.Reque
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

if args.Datacenter == "" {
args.Datacenter = s.agent.config.Datacenter
@@ -929,9 +968,11 @@ func (s *HTTPServer) ACLAuthMethodWrite(resp http.ResponseWriter, req *http.Requ
Datacenter: s.agent.config.Datacenter,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.AuthMethod.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.AuthMethod.EnterpriseMeta); err != nil {
return nil, err
}

if err := decodeBody(req.Body, &args.AuthMethod); err != nil {
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.AuthMethod)); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("AuthMethod decoding failed: %v", err)}
}

@@ -958,7 +999,9 @@ func (s *HTTPServer) ACLAuthMethodDelete(resp http.ResponseWriter, req *http.Req
AuthMethodName: methodName,
}
s.parseToken(req, &args.Token)
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

var ignored bool
if err := s.agent.RPC("ACL.AuthMethodDelete", args, &ignored); err != nil {
@@ -978,10 +1021,12 @@ func (s *HTTPServer) ACLLogin(resp http.ResponseWriter, req *http.Request) (inte
Auth: &structs.ACLLoginParams{},
}
s.parseDC(req, &args.Datacenter)
s.parseEntMeta(req, &args.Auth.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.Auth.EnterpriseMeta); err != nil {
return nil, err
}

if err := decodeBody(req.Body, &args.Auth); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Failed to decode request body: %v", err)}
if err := s.rewordUnknownEnterpriseFieldError(lib.DecodeJSON(req.Body, &args.Auth)); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Failed to decode request body:: %v", err)}
}

var out structs.ACLToken
@@ -3055,7 +3055,7 @@ func testCreateToken(t *testing.T, a *TestAgent, rules string) string {
policyID := testCreatePolicy(t, a, policyName, rules)

args := map[string]interface{}{
"Name": "User Token",
"Description": "User Token",
"Policies": []map[string]interface{}{
map[string]interface{}{
"ID": policyID,
@@ -1,7 +1,6 @@
package agent

import (
"encoding/json"
"fmt"
"net/http"
"strings"
@@ -107,7 +106,7 @@ func (t *discoveryChainReadRequest) UnmarshalJSON(data []byte) (err error) {
}{
Alias: (*Alias)(t),
}
if err = json.Unmarshal(data, &aux); err != nil {
if err = lib.UnmarshalJSON(data, &aux); err != nil {
return err
}

@@ -25,6 +25,7 @@ import (
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/go-cleanhttp"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
@@ -573,11 +574,7 @@ func (s *HTTPServer) Index(resp http.ResponseWriter, req *http.Request) {
}

func decodeBody(body io.Reader, out interface{}) error {
if body == nil {
return io.EOF
}

return json.NewDecoder(body).Decode(&out)
return lib.DecodeJSON(body, out)
}

// decodeBodyDeprecated is deprecated, please ues decodeBody above.
@@ -3,10 +3,38 @@
package agent

import (
"fmt"
"net/http"
"strings"

"github.com/hashicorp/consul/agent/structs"
)

func (s *HTTPServer) parseEntMeta(req *http.Request, entMeta *structs.EnterpriseMeta) {
func (s *HTTPServer) parseEntMeta(req *http.Request, entMeta *structs.EnterpriseMeta) error {
if headerNS := req.Header.Get("X-Consul-Namespace"); headerNS != "" {
return BadRequestError{Reason: "Invalid header: \"X-Consul-Namespace\" - Namespaces is a Consul Enterprise feature"}
}
if queryNS := req.URL.Query().Get("ns"); queryNS != "" {
return BadRequestError{Reason: "Invalid query parameter: \"ns\" - Namespaces is a Consul Enterprise feature"}
}
return nil
}

func (s *HTTPServer) rewordUnknownEnterpriseFieldError(err error) error {
if err == nil {
return nil
}

msg := err.Error()

if strings.Contains(msg, "json: unknown field ") {
quotedField := strings.TrimPrefix(msg, "json: unknown field ")

switch quotedField {
case `"Namespace"`:
return fmt.Errorf("%v - Namespaces is a Consul Enterprise feature", err)
}
}

return err
}
@@ -18,7 +18,9 @@ func (s *HTTPServer) KVSEndpoint(resp http.ResponseWriter, req *http.Request) (i
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
s.parseEntMeta(req, &args.EnterpriseMeta)
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}

// Pull out the key name, validation left to each sub-handler
args.Key = strings.TrimPrefix(req.URL.Path, "/v1/kv/")

0 comments on commit a704ebe

Please sign in to comment.
You can’t perform that action at this time.