mirror of https://github.com/status-im/consul.git
[NET-5333] Add api to read/list and preview templated policies (#18748)
This commit is contained in:
parent
b1688ad856
commit
9776c10efb
|
@ -1133,3 +1133,150 @@ func (s *HTTPHandlers) ACLAuthorize(resp http.ResponseWriter, req *http.Request)
|
|||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
type ACLTemplatedPolicyResponse struct {
|
||||
TemplateName string
|
||||
Schema string
|
||||
Template string
|
||||
}
|
||||
|
||||
func (s *HTTPHandlers) ACLTemplatedPoliciesList(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if s.checkACLDisabled() {
|
||||
return nil, aclDisabled
|
||||
}
|
||||
|
||||
var token string
|
||||
s.parseToken(req, &token)
|
||||
|
||||
var entMeta acl.EnterpriseMeta
|
||||
if err := s.parseEntMetaNoWildcard(req, &entMeta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.defaultMetaPartitionToAgent(&entMeta)
|
||||
var authzContext acl.AuthorizerContext
|
||||
authz, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, &entMeta, &authzContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only ACLRead privileges are required to list templated policies
|
||||
if err := authz.ToAllowAuthorizer().ACLReadAllowed(&authzContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
templatedPolicies := make(map[string]ACLTemplatedPolicyResponse)
|
||||
|
||||
for tp, tmpBase := range structs.GetACLTemplatedPolicyList() {
|
||||
templatedPolicies[tp] = ACLTemplatedPolicyResponse{
|
||||
TemplateName: tmpBase.TemplateName,
|
||||
Schema: tmpBase.Schema,
|
||||
Template: tmpBase.Template,
|
||||
}
|
||||
}
|
||||
|
||||
return templatedPolicies, nil
|
||||
}
|
||||
|
||||
func (s *HTTPHandlers) ACLTemplatedPolicyRead(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if s.checkACLDisabled() {
|
||||
return nil, aclDisabled
|
||||
}
|
||||
|
||||
templateName := strings.TrimPrefix(req.URL.Path, "/v1/acl/templated-policy/name/")
|
||||
if templateName == "" {
|
||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing templated policy Name"}
|
||||
}
|
||||
|
||||
var token string
|
||||
s.parseToken(req, &token)
|
||||
|
||||
var entMeta acl.EnterpriseMeta
|
||||
if err := s.parseEntMetaNoWildcard(req, &entMeta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.defaultMetaPartitionToAgent(&entMeta)
|
||||
var authzContext acl.AuthorizerContext
|
||||
authz, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, &entMeta, &authzContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only ACLRead privileges are required to read templated policies
|
||||
if err := authz.ToAllowAuthorizer().ACLReadAllowed(&authzContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseTemplate, ok := structs.GetACLTemplatedPolicyBase(templateName)
|
||||
if !ok {
|
||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Invalid templated policy Name: %s", templateName)}
|
||||
}
|
||||
|
||||
return ACLTemplatedPolicyResponse{
|
||||
TemplateName: baseTemplate.TemplateName,
|
||||
Schema: baseTemplate.Schema,
|
||||
Template: baseTemplate.Template,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *HTTPHandlers) ACLTemplatedPolicyPreview(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if s.checkACLDisabled() {
|
||||
return nil, aclDisabled
|
||||
}
|
||||
|
||||
templateName := strings.TrimPrefix(req.URL.Path, "/v1/acl/templated-policy/preview/")
|
||||
if templateName == "" {
|
||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing templated policy Name"}
|
||||
}
|
||||
|
||||
var token string
|
||||
s.parseToken(req, &token)
|
||||
|
||||
var entMeta acl.EnterpriseMeta
|
||||
if err := s.parseEntMetaNoWildcard(req, &entMeta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.defaultMetaPartitionToAgent(&entMeta)
|
||||
var authzContext acl.AuthorizerContext
|
||||
authz, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, &entMeta, &authzContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only ACLRead privileges are required to read/preview templated policies
|
||||
if err := authz.ToAllowAuthorizer().ACLReadAllowed(&authzContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseTemplate, ok := structs.GetACLTemplatedPolicyBase(templateName)
|
||||
if !ok {
|
||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("templated policy %q does not exist", templateName)}
|
||||
}
|
||||
|
||||
var tpRequest structs.ACLTemplatedPolicyVariables
|
||||
|
||||
if err := decodeBody(req.Body, &tpRequest); err != nil {
|
||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Failed to decode request body: %s", err.Error())}
|
||||
}
|
||||
|
||||
templatedPolicy := structs.ACLTemplatedPolicy{
|
||||
TemplateID: baseTemplate.TemplateID,
|
||||
TemplateName: baseTemplate.TemplateName,
|
||||
TemplateVariables: &tpRequest,
|
||||
}
|
||||
|
||||
err = templatedPolicy.ValidateTemplatedPolicy(baseTemplate.Schema)
|
||||
if err != nil {
|
||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("validation error for templated policy: %q: %s", templatedPolicy.TemplateName, err.Error())}
|
||||
}
|
||||
|
||||
renderedPolicy, err := templatedPolicy.SyntheticPolicy(&entMeta)
|
||||
|
||||
if err != nil {
|
||||
return nil, HTTPError{StatusCode: http.StatusInternalServerError, Reason: fmt.Sprintf("Failed to generate synthetic policy: %q: %s", templatedPolicy.TemplateName, err.Error())}
|
||||
}
|
||||
|
||||
return renderedPolicy, nil
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/agent/consul/authmethod/testauth"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
|
@ -1361,6 +1362,88 @@ func TestACL_HTTP(t *testing.T) {
|
|||
require.Equal(t, "sn1", token.ServiceIdentities[0].ServiceName)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ACLTemplatedPolicy", func(t *testing.T) {
|
||||
t.Run("List", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/v1/acl/templated-policies", nil)
|
||||
req.Header.Add("X-Consul-Token", "root")
|
||||
resp := httptest.NewRecorder()
|
||||
a.srv.h.ServeHTTP(resp, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, resp.Code)
|
||||
|
||||
var list map[string]ACLTemplatedPolicyResponse
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&list))
|
||||
require.Len(t, list, 3)
|
||||
|
||||
require.Equal(t, ACLTemplatedPolicyResponse{
|
||||
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||
Schema: structs.ACLTemplatedPolicyIdentitiesSchema,
|
||||
Template: structs.ACLTemplatedPolicyService,
|
||||
}, list[api.ACLTemplatedPolicyServiceName])
|
||||
})
|
||||
t.Run("Read", func(t *testing.T) {
|
||||
t.Run("With non existing templated policy", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/v1/acl/templated-policy/name/fake", nil)
|
||||
req.Header.Add("X-Consul-Token", "root")
|
||||
resp := httptest.NewRecorder()
|
||||
a.srv.h.ServeHTTP(resp, req)
|
||||
require.Equal(t, http.StatusBadRequest, resp.Code)
|
||||
})
|
||||
|
||||
t.Run("With existing templated policy", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/v1/acl/templated-policy/name/"+api.ACLTemplatedPolicyDNSName, nil)
|
||||
req.Header.Add("X-Consul-Token", "root")
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
a.srv.h.ServeHTTP(resp, req)
|
||||
require.Equal(t, http.StatusOK, resp.Code)
|
||||
|
||||
var templatedPolicy ACLTemplatedPolicyResponse
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&templatedPolicy))
|
||||
require.Equal(t, structs.ACLTemplatedPolicyDNSSchema, templatedPolicy.Schema)
|
||||
require.Equal(t, api.ACLTemplatedPolicyDNSName, templatedPolicy.TemplateName)
|
||||
require.Equal(t, structs.ACLTemplatedPolicyDNS, templatedPolicy.Template)
|
||||
})
|
||||
})
|
||||
t.Run("preview", func(t *testing.T) {
|
||||
t.Run("When missing required variables", func(t *testing.T) {
|
||||
previewInput := &structs.ACLTemplatedPolicyVariables{}
|
||||
req, _ := http.NewRequest(
|
||||
"POST",
|
||||
fmt.Sprintf("/v1/acl/templated-policy/preview/%s", api.ACLTemplatedPolicyServiceName),
|
||||
jsonBody(previewInput),
|
||||
)
|
||||
req.Header.Add("X-Consul-Token", "root")
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
a.srv.h.ServeHTTP(resp, req)
|
||||
require.Equal(t, http.StatusBadRequest, resp.Code)
|
||||
})
|
||||
|
||||
t.Run("Correct input", func(t *testing.T) {
|
||||
previewInput := &structs.ACLTemplatedPolicyVariables{Name: "web"}
|
||||
req, _ := http.NewRequest(
|
||||
"POST",
|
||||
fmt.Sprintf("/v1/acl/templated-policy/preview/%s", api.ACLTemplatedPolicyServiceName),
|
||||
jsonBody(previewInput),
|
||||
)
|
||||
req.Header.Add("X-Consul-Token", "root")
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
a.srv.h.ServeHTTP(resp, req)
|
||||
require.Equal(t, http.StatusOK, resp.Code)
|
||||
|
||||
var syntheticPolicy *structs.ACLPolicy
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&syntheticPolicy))
|
||||
|
||||
require.NotEmpty(t, syntheticPolicy.ID)
|
||||
require.NotEmpty(t, syntheticPolicy.Hash)
|
||||
require.Equal(t, "synthetic policy generated from templated policy: builtin/service", syntheticPolicy.Description)
|
||||
require.Contains(t, syntheticPolicy.Name, "synthetic-policy-")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestACL_LoginProcedure_HTTP(t *testing.T) {
|
||||
|
|
|
@ -26,6 +26,9 @@ func init() {
|
|||
registerEndpoint("/v1/acl/token", []string{"PUT"}, (*HTTPHandlers).ACLTokenCreate)
|
||||
registerEndpoint("/v1/acl/token/self", []string{"GET"}, (*HTTPHandlers).ACLTokenSelf)
|
||||
registerEndpoint("/v1/acl/token/", []string{"GET", "PUT", "DELETE"}, (*HTTPHandlers).ACLTokenCRUD)
|
||||
registerEndpoint("/v1/acl/templated-policies", []string{"GET"}, (*HTTPHandlers).ACLTemplatedPoliciesList)
|
||||
registerEndpoint("/v1/acl/templated-policy/name/", []string{"GET"}, (*HTTPHandlers).ACLTemplatedPolicyRead)
|
||||
registerEndpoint("/v1/acl/templated-policy/preview/", []string{"POST"}, (*HTTPHandlers).ACLTemplatedPolicyPreview)
|
||||
registerEndpoint("/v1/agent/token/", []string{"PUT"}, (*HTTPHandlers).AgentToken)
|
||||
registerEndpoint("/v1/agent/self", []string{"GET"}, (*HTTPHandlers).AgentSelf)
|
||||
registerEndpoint("/v1/agent/host", []string{"GET"}, (*HTTPHandlers).AgentHost)
|
||||
|
|
|
@ -263,7 +263,19 @@ func (tps ACLTemplatedPolicies) Deduplicate() ACLTemplatedPolicies {
|
|||
}
|
||||
|
||||
func GetACLTemplatedPolicyBase(templateName string) (*ACLTemplatedPolicyBase, bool) {
|
||||
baseTemplate, found := aclTemplatedPoliciesList[templateName]
|
||||
if orig, found := aclTemplatedPoliciesList[templateName]; found {
|
||||
copy := *orig
|
||||
return ©, found
|
||||
}
|
||||
|
||||
return baseTemplate, found
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func GetACLTemplatedPolicyList() map[string]*ACLTemplatedPolicyBase {
|
||||
m := make(map[string]*ACLTemplatedPolicyBase, len(aclTemplatedPoliciesList))
|
||||
for k, v := range aclTemplatedPoliciesList {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue