mirror of https://github.com/status-im/consul.git
[NET-5334] Added CLI commands for templated policies (#18816)
This commit is contained in:
parent
802122640b
commit
1afeb6e040
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
cli: Add `consul acl templated-policy` commands to read, list and preview templated policies.
|
||||
```
|
|
@ -1134,12 +1134,6 @@ 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
|
||||
|
@ -1165,10 +1159,10 @@ func (s *HTTPHandlers) ACLTemplatedPoliciesList(resp http.ResponseWriter, req *h
|
|||
return nil, err
|
||||
}
|
||||
|
||||
templatedPolicies := make(map[string]ACLTemplatedPolicyResponse)
|
||||
templatedPolicies := make(map[string]api.ACLTemplatedPolicyResponse)
|
||||
|
||||
for tp, tmpBase := range structs.GetACLTemplatedPolicyList() {
|
||||
templatedPolicies[tp] = ACLTemplatedPolicyResponse{
|
||||
templatedPolicies[tp] = api.ACLTemplatedPolicyResponse{
|
||||
TemplateName: tmpBase.TemplateName,
|
||||
Schema: tmpBase.Schema,
|
||||
Template: tmpBase.Template,
|
||||
|
@ -1213,7 +1207,7 @@ func (s *HTTPHandlers) ACLTemplatedPolicyRead(resp http.ResponseWriter, req *htt
|
|||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Invalid templated policy Name: %s", templateName)}
|
||||
}
|
||||
|
||||
return ACLTemplatedPolicyResponse{
|
||||
return api.ACLTemplatedPolicyResponse{
|
||||
TemplateName: baseTemplate.TemplateName,
|
||||
Schema: baseTemplate.Schema,
|
||||
Template: baseTemplate.Template,
|
||||
|
|
|
@ -1372,11 +1372,11 @@ func TestACL_HTTP(t *testing.T) {
|
|||
|
||||
require.Equal(t, http.StatusOK, resp.Code)
|
||||
|
||||
var list map[string]ACLTemplatedPolicyResponse
|
||||
var list map[string]api.ACLTemplatedPolicyResponse
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&list))
|
||||
require.Len(t, list, 3)
|
||||
|
||||
require.Equal(t, ACLTemplatedPolicyResponse{
|
||||
require.Equal(t, api.ACLTemplatedPolicyResponse{
|
||||
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||
Schema: structs.ACLTemplatedPolicyIdentitiesSchema,
|
||||
Template: structs.ACLTemplatedPolicyService,
|
||||
|
@ -1399,7 +1399,7 @@ func TestACL_HTTP(t *testing.T) {
|
|||
a.srv.h.ServeHTTP(resp, req)
|
||||
require.Equal(t, http.StatusOK, resp.Code)
|
||||
|
||||
var templatedPolicy ACLTemplatedPolicyResponse
|
||||
var templatedPolicy api.ACLTemplatedPolicyResponse
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&templatedPolicy))
|
||||
require.Equal(t, structs.ACLTemplatedPolicyDNSSchema, templatedPolicy.Schema)
|
||||
require.Equal(t, api.ACLTemplatedPolicyDNSName, templatedPolicy.TemplateName)
|
||||
|
|
|
@ -23,19 +23,20 @@ type ACLTemplatedPolicies []*ACLTemplatedPolicy
|
|||
const (
|
||||
ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004"
|
||||
ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003"
|
||||
ACLTemplatedPolicyIdentitiesSchema = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "$ref": "#/definitions/min-length-one" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"definitions": {
|
||||
"min-length-one": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
ACLTemplatedPolicyIdentitiesSchema = `
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "$ref": "#/definitions/min-length-one" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"definitions": {
|
||||
"min-length-one": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}`
|
||||
}
|
||||
}`
|
||||
|
||||
ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005"
|
||||
ACLTemplatedPolicyDNSSchema = "" // empty schema as it does not require variables
|
||||
|
@ -51,8 +52,9 @@ type ACLTemplatedPolicyBase struct {
|
|||
}
|
||||
|
||||
var (
|
||||
// TODO(Ronald): add other templates
|
||||
// This supports: node, service and dns templates
|
||||
// Note: when adding a new builtin template, ensure you update `command/acl/templatedpolicy/formatter.go`
|
||||
// to handle the new templates required variables and schema.
|
||||
aclTemplatedPoliciesList = map[string]*ACLTemplatedPolicyBase{
|
||||
api.ACLTemplatedPolicyServiceName: {
|
||||
TemplateID: ACLTemplatedPolicyServiceID,
|
||||
|
|
81
api/acl.go
81
api/acl.go
|
@ -166,6 +166,12 @@ type ACLTemplatedPolicy struct {
|
|||
Datacenters []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ACLTemplatedPolicyResponse struct {
|
||||
TemplateName string
|
||||
Schema string
|
||||
Template string
|
||||
}
|
||||
|
||||
type ACLTemplatedPolicyVariables struct {
|
||||
Name string
|
||||
}
|
||||
|
@ -1653,3 +1659,78 @@ func (a *ACL) OIDCCallback(auth *ACLOIDCCallbackParams, q *WriteOptions) (*ACLTo
|
|||
}
|
||||
return &out, wm, nil
|
||||
}
|
||||
|
||||
// TemplatedPolicyReadByName retrieves the templated policy details (by name). Returns nil if not found.
|
||||
func (a *ACL) TemplatedPolicyReadByName(templateName string, q *QueryOptions) (*ACLTemplatedPolicyResponse, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/templated-policy/name/"+templateName)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := a.c.doRequest(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer closeResponseBody(resp)
|
||||
found, resp, err := requireNotFoundOrOK(resp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
if !found {
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
var out ACLTemplatedPolicyResponse
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &out, qm, nil
|
||||
}
|
||||
|
||||
// TemplatedPolicyList retrieves a listing of all templated policies.
|
||||
func (a *ACL) TemplatedPolicyList(q *QueryOptions) (map[string]ACLTemplatedPolicyResponse, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/templated-policies")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := a.c.doRequest(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer closeResponseBody(resp)
|
||||
if err := requireOK(resp); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries map[string]ACLTemplatedPolicyResponse
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// TemplatedPolicyPreview is used to preview the policy rendered by the templated policy.
|
||||
func (a *ACL) TemplatedPolicyPreview(tp *ACLTemplatedPolicy, q *WriteOptions) (*ACLPolicy, *WriteMeta, error) {
|
||||
r := a.c.newRequest("POST", "/v1/acl/templated-policy/preview/"+tp.TemplateName)
|
||||
r.setWriteOptions(q)
|
||||
r.obj = tp.TemplateVariables
|
||||
|
||||
rtt, resp, err := a.c.doRequest(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer closeResponseBody(resp)
|
||||
if err := requireOK(resp); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out ACLPolicy
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &out, wm, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
PrettyFormat string = "pretty"
|
||||
JSONFormat string = "json"
|
||||
WhitespaceIndent = "\t"
|
||||
)
|
||||
|
||||
// Formatter defines methods provided by templated-policy command output formatter
|
||||
type Formatter interface {
|
||||
FormatTemplatedPolicy(policy api.ACLTemplatedPolicyResponse) (string, error)
|
||||
FormatTemplatedPolicyList(policies map[string]api.ACLTemplatedPolicyResponse) (string, error)
|
||||
}
|
||||
|
||||
// GetSupportedFormats returns supported formats
|
||||
func GetSupportedFormats() []string {
|
||||
return []string{PrettyFormat, JSONFormat}
|
||||
}
|
||||
|
||||
// NewFormatter returns Formatter implementation
|
||||
func NewFormatter(format string, showMeta bool) (formatter Formatter, err error) {
|
||||
switch format {
|
||||
case PrettyFormat:
|
||||
formatter = newPrettyFormatter(showMeta)
|
||||
case JSONFormat:
|
||||
formatter = newJSONFormatter(showMeta)
|
||||
default:
|
||||
err = fmt.Errorf("unknown format: %q", format)
|
||||
}
|
||||
|
||||
return formatter, err
|
||||
}
|
||||
|
||||
func newPrettyFormatter(showMeta bool) Formatter {
|
||||
return &prettyFormatter{showMeta}
|
||||
}
|
||||
|
||||
func newJSONFormatter(showMeta bool) Formatter {
|
||||
return &jsonFormatter{showMeta}
|
||||
}
|
||||
|
||||
type prettyFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
// FormatTemplatedPolicy displays template name, input variables and example usages. When
|
||||
// showMeta is true, we display raw template code and schema.
|
||||
// This implementation is a conscious choice as we know builtin variables we know every required/optional input variables
|
||||
// so we can just hardcode this.
|
||||
// In the future, when we implement user defined templated policies, we will move this to some sort of schema parsing.
|
||||
// This implementation allows us to move forward without limiting ourselves when implementing user defined templated policies.
|
||||
func (f *prettyFormatter) FormatTemplatedPolicy(templatedPolicy api.ACLTemplatedPolicyResponse) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("Name: %s\n", templatedPolicy.TemplateName))
|
||||
|
||||
buffer.WriteString("Input variables:")
|
||||
switch templatedPolicy.TemplateName {
|
||||
case api.ACLTemplatedPolicyServiceName:
|
||||
buffer.WriteString(fmt.Sprintf("\n%sName: String - Required - The name of the service.\n", WhitespaceIndent))
|
||||
buffer.WriteString("Example usage:\n")
|
||||
buffer.WriteString(WhitespaceIndent + "consul acl token create -templated-policy builtin/service -var name:api\n")
|
||||
case api.ACLTemplatedPolicyNodeName:
|
||||
buffer.WriteString(fmt.Sprintf("\n%sName: String - Required - The node name.\n", WhitespaceIndent))
|
||||
buffer.WriteString("Example usage:\n")
|
||||
buffer.WriteString(fmt.Sprintf("%sconsul acl token create -templated-policy builtin/node -var name:node-1\n", WhitespaceIndent))
|
||||
case api.ACLTemplatedPolicyDNSName:
|
||||
buffer.WriteString(" None\n")
|
||||
buffer.WriteString("Example usage:\n")
|
||||
buffer.WriteString(fmt.Sprintf("%sconsul acl token create -templated-policy builtin/dns\n", WhitespaceIndent))
|
||||
default:
|
||||
buffer.WriteString(" None\n")
|
||||
}
|
||||
|
||||
if f.showMeta {
|
||||
if templatedPolicy.Schema != "" {
|
||||
buffer.WriteString(fmt.Sprintf("Schema:\n%s\n\n", templatedPolicy.Schema))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("Raw Template:\n%s\n", templatedPolicy.Template))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatTemplatedPolicyList(policies map[string]api.ACLTemplatedPolicyResponse) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
templateNames := make([]string, 0, len(policies))
|
||||
for _, templatedPolicy := range policies {
|
||||
templateNames = append(templateNames, templatedPolicy.TemplateName)
|
||||
}
|
||||
|
||||
//ensure the list is consistently sorted by strings
|
||||
sort.Strings(templateNames)
|
||||
for _, name := range templateNames {
|
||||
buffer.WriteString(fmt.Sprintf("%s\n", name))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatTemplatedPolicy(templatedPolicy api.ACLTemplatedPolicyResponse) (string, error) {
|
||||
b, err := json.MarshalIndent(templatedPolicy, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal templated policy: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatTemplatedPolicyList(templatedPolicies map[string]api.ACLTemplatedPolicyResponse) (string, error) {
|
||||
b, err := json.MarshalIndent(templatedPolicies, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal templated policies: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build !consulent
|
||||
// +build !consulent
|
||||
|
||||
package templatedpolicy
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFormatTemplatedPolicy(t *testing.T) {
|
||||
testFormatTemplatedPolicy(t, "FormatTemplatedPolicy/ce")
|
||||
}
|
||||
|
||||
func TestFormatTemplatedPolicyList(t *testing.T) {
|
||||
testFormatTemplatedPolicyList(t, "FormatTemplatedPolicyList/ce")
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// golden reads from the golden file returning the contents as a string.
|
||||
func golden(t *testing.T, name string) string {
|
||||
t.Helper()
|
||||
|
||||
golden := filepath.Join("testdata", name+".golden")
|
||||
expected, err := os.ReadFile(golden)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(expected)
|
||||
}
|
||||
|
||||
func testFormatTemplatedPolicy(t *testing.T, dirPath string) {
|
||||
type testCase struct {
|
||||
templatedPolicy api.ACLTemplatedPolicyResponse
|
||||
}
|
||||
|
||||
cases := map[string]testCase{
|
||||
"node-templated-policy": {
|
||||
templatedPolicy: api.ACLTemplatedPolicyResponse{
|
||||
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||
Schema: structs.ACLTemplatedPolicyIdentitiesSchema,
|
||||
Template: structs.ACLTemplatedPolicyNode,
|
||||
},
|
||||
},
|
||||
"dns-templated-policy": {
|
||||
templatedPolicy: api.ACLTemplatedPolicyResponse{
|
||||
TemplateName: api.ACLTemplatedPolicyDNSName,
|
||||
Schema: structs.ACLTemplatedPolicyDNSSchema,
|
||||
Template: structs.ACLTemplatedPolicyDNS,
|
||||
},
|
||||
},
|
||||
"service-templated-policy": {
|
||||
templatedPolicy: api.ACLTemplatedPolicyResponse{
|
||||
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||
Schema: structs.ACLTemplatedPolicyIdentitiesSchema,
|
||||
Template: structs.ACLTemplatedPolicyService,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
formatters := map[string]Formatter{
|
||||
"pretty": newPrettyFormatter(false),
|
||||
"pretty-meta": newPrettyFormatter(true),
|
||||
// the JSON formatter ignores the showMeta
|
||||
"json": newJSONFormatter(false),
|
||||
}
|
||||
|
||||
for name, tcase := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
for fmtName, formatter := range formatters {
|
||||
t.Run(fmtName, func(t *testing.T) {
|
||||
actual, err := formatter.FormatTemplatedPolicy(tcase.templatedPolicy)
|
||||
require.NoError(t, err)
|
||||
|
||||
gName := fmt.Sprintf("%s.%s", name, fmtName)
|
||||
|
||||
expected := golden(t, path.Join(dirPath, gName))
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testFormatTemplatedPolicyList(t *testing.T, dirPath string) {
|
||||
// we don't consider the showMeta field for policy list
|
||||
formatters := map[string]Formatter{
|
||||
"pretty": newPrettyFormatter(false),
|
||||
"json": newJSONFormatter(false),
|
||||
}
|
||||
|
||||
policies := map[string]api.ACLTemplatedPolicyResponse{
|
||||
"builtin/node": {
|
||||
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||
Schema: structs.ACLTemplatedPolicyIdentitiesSchema,
|
||||
Template: structs.ACLTemplatedPolicyNode,
|
||||
},
|
||||
"builtin/dns": {
|
||||
TemplateName: api.ACLTemplatedPolicyDNSName,
|
||||
Schema: structs.ACLTemplatedPolicyDNSSchema,
|
||||
Template: structs.ACLTemplatedPolicyDNS,
|
||||
},
|
||||
"builtin/service": {
|
||||
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||
Schema: structs.ACLTemplatedPolicyIdentitiesSchema,
|
||||
Template: structs.ACLTemplatedPolicyService,
|
||||
},
|
||||
}
|
||||
|
||||
for fmtName, formatter := range formatters {
|
||||
t.Run(fmtName, func(t *testing.T) {
|
||||
actual, err := formatter.FormatTemplatedPolicyList(policies)
|
||||
require.NoError(t, err)
|
||||
|
||||
gName := fmt.Sprintf("list.%s", fmtName)
|
||||
|
||||
expected := golden(t, path.Join(dirPath, gName))
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicylist
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl/templatedpolicy"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
)
|
||||
|
||||
func New(ui cli.Ui) *cmd {
|
||||
c := &cmd{UI: ui}
|
||||
c.init()
|
||||
return c
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
UI cli.Ui
|
||||
flags *flag.FlagSet
|
||||
http *flags.HTTPFlags
|
||||
help string
|
||||
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
templatedpolicy.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(templatedpolicy.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
flags.Merge(c.flags, c.http.MultiTenancyFlags())
|
||||
c.help = flags.Usage(help, c.flags)
|
||||
}
|
||||
|
||||
func (c *cmd) Run(args []string) int {
|
||||
if err := c.flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.http.APIClient()
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
tps, _, err := client.ACL().TemplatedPolicyList(nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to retrieve the templated policies list: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
formatter, err := templatedpolicy.NewFormatter(c.format, false)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatTemplatedPolicyList(tps)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *cmd) Synopsis() string {
|
||||
return synopsis
|
||||
}
|
||||
|
||||
func (c *cmd) Help() string {
|
||||
return flags.Usage(c.help, nil)
|
||||
}
|
||||
|
||||
const (
|
||||
synopsis = "Lists ACL templated policies"
|
||||
help = `
|
||||
Usage: consul acl templated-policy list [options]
|
||||
|
||||
Lists all the ACL templated policies.
|
||||
|
||||
Example:
|
||||
|
||||
$ consul acl templated-policy list
|
||||
`
|
||||
)
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicylist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/agent"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTemplatedPolicyListCommand_noTabs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
|
||||
t.Fatal("help has tabs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplatedPolicyListCommand(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
a := agent.NewTestAgent(t, `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
initial_management = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 0)
|
||||
assert.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, api.ACLTemplatedPolicyServiceName)
|
||||
require.Contains(t, output, api.ACLTemplatedPolicyDNSName)
|
||||
}
|
||||
|
||||
func TestTemplatedPolicyListCommand_JSON(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
a := agent.NewTestAgent(t, `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
initial_management = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 0)
|
||||
assert.Empty(t, ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, api.ACLTemplatedPolicyServiceName)
|
||||
require.Contains(t, output, api.ACLTemplatedPolicyDNSName)
|
||||
|
||||
var jsonOutput map[string]api.ACLTemplatedPolicyResponse
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
outputTemplate := jsonOutput[api.ACLTemplatedPolicyDNSName]
|
||||
assert.Equal(t, structs.ACLTemplatedPolicyDNSSchema, outputTemplate.Schema)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicylist
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/policy"
|
||||
"github.com/hashicorp/consul/command/acl/templatedpolicy"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
)
|
||||
|
||||
func New(ui cli.Ui) *cmd {
|
||||
c := &cmd{UI: ui}
|
||||
c.init()
|
||||
return c
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
UI cli.Ui
|
||||
flags *flag.FlagSet
|
||||
http *flags.HTTPFlags
|
||||
help string
|
||||
|
||||
templatedPolicyName string
|
||||
templatedPolicyFile string
|
||||
templatedPolicyVariables []string
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
templatedpolicy.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(templatedpolicy.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.flags.Var((*flags.AppendSliceValue)(&c.templatedPolicyVariables), "var", "Templated policy variables."+
|
||||
" Must be used in combination with -name flag to specify required variables."+
|
||||
" May be specified multiple times with different variables."+
|
||||
" Format is VariableName:Value")
|
||||
c.flags.StringVar(&c.templatedPolicyName, "name", "", "The templated policy name. Use -var flag to specify variables when required.")
|
||||
c.flags.StringVar(&c.templatedPolicyFile, "file", "", "Path to a file containing templated policies and variables.")
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
flags.Merge(c.flags, c.http.MultiTenancyFlags())
|
||||
c.help = flags.Usage(help, c.flags)
|
||||
}
|
||||
|
||||
func (c *cmd) Run(args []string) int {
|
||||
if err := c.flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(c.templatedPolicyName) == 0 && len(c.templatedPolicyFile) == 0 {
|
||||
c.UI.Error("Cannot preview a templated policy without specifying -name or -file")
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.http.APIClient()
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
parsedTemplatedPolicies, err := acl.ExtractTemplatedPolicies(c.templatedPolicyName, c.templatedPolicyFile, c.templatedPolicyVariables)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
if !(len(parsedTemplatedPolicies) == 1) {
|
||||
c.UI.Error("Can only preview a single templated policy at a time.")
|
||||
return 1
|
||||
}
|
||||
|
||||
syntheticPolicy, _, err := client.ACL().TemplatedPolicyPreview(parsedTemplatedPolicies[0], nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to generate the templated policy preview: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
formatter, err := policy.NewFormatter(c.format, false)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatPolicy(syntheticPolicy)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *cmd) Synopsis() string {
|
||||
return synopsis
|
||||
}
|
||||
|
||||
func (c *cmd) Help() string {
|
||||
return flags.Usage(c.help, nil)
|
||||
}
|
||||
|
||||
const (
|
||||
synopsis = "Preview the policy rendered by the ACL templated policy"
|
||||
help = `
|
||||
Usage: consul acl templated-policy preview [options]
|
||||
|
||||
Preview the policy rendered by the ACL templated policy.
|
||||
|
||||
Example:
|
||||
|
||||
$ consul acl templated-policy preview -name "builtin/service" -var "name:api"
|
||||
|
||||
Preview a templated policy using a file.
|
||||
|
||||
Example:
|
||||
|
||||
$ consul acl templated-policy preview -file templated-policy-file.hcl
|
||||
`
|
||||
)
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicylist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/agent"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTemplatedPolicyPreviewCommand_noTabs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
|
||||
t.Fatal("help has tabs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplatedPolicyPreviewCommand(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
|
||||
a := agent.NewTestAgent(t, `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
initial_management = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||
|
||||
t.Run("missing name and file flags", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 1)
|
||||
assert.Contains(t, ui.ErrorWriter.String(), "Cannot preview a templated policy without specifying -name or -file")
|
||||
})
|
||||
|
||||
t.Run("missing required template variables", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=builtin/node",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 1)
|
||||
assert.Contains(t, ui.ErrorWriter.String(), "Failed to generate the templated policy preview")
|
||||
})
|
||||
|
||||
t.Run("correct input", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=builtin/node",
|
||||
"-var=name:api",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 0)
|
||||
assert.Empty(t, ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, "synthetic policy generated from templated policy: builtin/node")
|
||||
})
|
||||
|
||||
t.Run("correct input with file", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-file=" + testDir + "/templated-policy.hcl",
|
||||
}
|
||||
|
||||
templatedPolicy := []byte("TemplatedPolicy \"builtin/service\" { Name = \"web\"}")
|
||||
err := os.WriteFile(testDir+"/templated-policy.hcl", templatedPolicy, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 0)
|
||||
assert.Empty(t, ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, "synthetic policy generated from templated policy: builtin/service")
|
||||
})
|
||||
|
||||
t.Run("multiple templated policies input in file", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-file=" + testDir + "/templated-policy.hcl",
|
||||
}
|
||||
|
||||
templatedPolicy := []byte(`
|
||||
TemplatedPolicy "builtin/service" { Name = "web"}
|
||||
TemplatedPolicy "builtin/node" { Name = "api"}
|
||||
`)
|
||||
err := os.WriteFile(testDir+"/templated-policy.hcl", templatedPolicy, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 1)
|
||||
assert.Contains(t, ui.ErrorWriter.String(), "Can only preview a single templated policy at a time.")
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplatedPolicyPreviewCommand_JSON(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
a := agent.NewTestAgent(t, `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
initial_management = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||
|
||||
t.Run("missing templated-policy flags", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 1)
|
||||
assert.Contains(t, ui.ErrorWriter.String(), "Cannot preview a templated policy without specifying -name or -file")
|
||||
})
|
||||
|
||||
t.Run("missing required template variables", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=builtin/node",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 1)
|
||||
assert.Contains(t, ui.ErrorWriter.String(), "Failed to generate the templated policy preview")
|
||||
})
|
||||
|
||||
t.Run("correct input", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=builtin/node",
|
||||
"-var=name:api",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 0)
|
||||
assert.Empty(t, ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, "synthetic policy generated from templated policy: builtin/node")
|
||||
|
||||
// ensure valid json
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicyread
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl/templatedpolicy"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
)
|
||||
|
||||
const (
|
||||
PrettyFormat string = "pretty"
|
||||
JSONFormat string = "json"
|
||||
synopsis = "Read an ACL Templated Policy"
|
||||
help = `
|
||||
Usage: consul acl templated-policy read [options] TEMPLATED_POLICY
|
||||
|
||||
This command will retrieve and print out the details of a single templated policy.
|
||||
|
||||
Example:
|
||||
|
||||
$ consul acl templated-policy read -name templated-policy-name
|
||||
`
|
||||
)
|
||||
|
||||
func New(ui cli.Ui) *cmd {
|
||||
c := &cmd{UI: ui}
|
||||
c.init()
|
||||
return c
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
UI cli.Ui
|
||||
flags *flag.FlagSet
|
||||
http *flags.HTTPFlags
|
||||
help string
|
||||
|
||||
templateName string
|
||||
format string
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
c.flags.StringVar(&c.templateName, "name", "", "The name of the templated policy to read.")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
templatedpolicy.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(templatedpolicy.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.flags.BoolVar(&c.showMeta, "meta", false, "Indicates that templated policy metadata such "+
|
||||
"as the schema and template code should be shown for each entry.")
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
flags.Merge(c.flags, c.http.MultiTenancyFlags())
|
||||
c.help = flags.Usage(help, c.flags)
|
||||
}
|
||||
|
||||
func (c *cmd) Run(args []string) int {
|
||||
if err := c.flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if c.templateName == "" {
|
||||
c.UI.Error("Must specify the -name parameter")
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.http.APIClient()
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
var tp *api.ACLTemplatedPolicyResponse
|
||||
|
||||
tp, _, err = client.ACL().TemplatedPolicyReadByName(c.templateName, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading templated policy %q: %v", c.templateName, err))
|
||||
return 1
|
||||
} else if tp == nil {
|
||||
c.UI.Error(fmt.Sprintf("Templated policy not found with name %q", c.templateName))
|
||||
return 1
|
||||
}
|
||||
|
||||
formatter, err := templatedpolicy.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatTemplatedPolicy(*tp)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *cmd) Synopsis() string {
|
||||
return synopsis
|
||||
}
|
||||
|
||||
func (c *cmd) Help() string {
|
||||
return flags.Usage(c.help, nil)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicyread
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/agent"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTemplatedPolicyReadCommand_noTabs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
|
||||
t.Fatal("help has tabs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplatedPolicyReadCommand(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
a := agent.NewTestAgent(t, `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
initial_management = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||
|
||||
t.Run("missing name flag", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 1)
|
||||
assert.Contains(t, ui.ErrorWriter.String(), "Must specify the -name parameter")
|
||||
})
|
||||
|
||||
t.Run("correct input", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=" + api.ACLTemplatedPolicyNodeName,
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 0)
|
||||
assert.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, "Name: String - Required - The node name.")
|
||||
require.Contains(t, output, "consul acl token create -templated-policy builtin/node -var name:node-1")
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplatedPolicyReadCommand_JSON(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
a := agent.NewTestAgent(t, `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
initial_management = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1", testrpc.WithToken("root"))
|
||||
|
||||
t.Run("missing name flag", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 1)
|
||||
assert.Contains(t, ui.ErrorWriter.String(), "Must specify the -name parameter")
|
||||
})
|
||||
|
||||
t.Run("correct input", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=" + api.ACLTemplatedPolicyNodeName,
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
cmd := New(ui)
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(t, code, 0)
|
||||
assert.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
var templatedPolicy api.ACLTemplatedPolicyResponse
|
||||
err := json.Unmarshal([]byte(output), &templatedPolicy)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, structs.ACLTemplatedPolicyIdentitiesSchema, templatedPolicy.Schema)
|
||||
assert.Equal(t, api.ACLTemplatedPolicyNodeName, templatedPolicy.TemplateName)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package templatedpolicy
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func New() *cmd {
|
||||
return &cmd{}
|
||||
}
|
||||
|
||||
type cmd struct{}
|
||||
|
||||
func (c *cmd) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
func (c *cmd) Synopsis() string {
|
||||
return synopsis
|
||||
}
|
||||
|
||||
func (c *cmd) Help() string {
|
||||
return flags.Usage(help, nil)
|
||||
}
|
||||
|
||||
const synopsis = "Manage Consul's ACL templated policies"
|
||||
const help = `
|
||||
Usage: consul acl templated-policy <subcommand> [options] [args]
|
||||
|
||||
This command has subcommands for managing Consul ACL templated policies.
|
||||
Here are some simple examples, and more detailed examples are available
|
||||
in the subcommands or the documentation.
|
||||
|
||||
List all templated policies:
|
||||
|
||||
$ consul acl templated-policy list
|
||||
|
||||
Preview the policy rendered by the ACL templated policy:
|
||||
|
||||
$ consul acl templated-policy preview -name "builtin/service" -var "name:api"
|
||||
|
||||
Read a templated policy with name:
|
||||
|
||||
$ consul acl templated-policy read -name "builtin/service"
|
||||
|
||||
For more examples, ask for subcommand help or view the documentation.
|
||||
`
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"TemplateName": "builtin/dns",
|
||||
"Schema": "",
|
||||
"Template": "\nnode_prefix \"\" {\n\tpolicy = \"read\"\n}\nservice_prefix \"\" {\n\tpolicy = \"read\"\n}\nquery_prefix \"\" {\n\tpolicy = \"read\"\n}"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
Name: builtin/dns
|
||||
Input variables: None
|
||||
Example usage:
|
||||
consul acl token create -templated-policy builtin/dns
|
||||
Raw Template:
|
||||
|
||||
node_prefix "" {
|
||||
policy = "read"
|
||||
}
|
||||
service_prefix "" {
|
||||
policy = "read"
|
||||
}
|
||||
query_prefix "" {
|
||||
policy = "read"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
Name: builtin/dns
|
||||
Input variables: None
|
||||
Example usage:
|
||||
consul acl token create -templated-policy builtin/dns
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"TemplateName": "builtin/node",
|
||||
"Schema": "\n{\n\t\"type\": \"object\",\n\t\"properties\": {\n\t\t\"name\": { \"type\": \"string\", \"$ref\": \"#/definitions/min-length-one\" }\n\t},\n\t\"required\": [\"name\"],\n\t\"definitions\": {\n\t\t\"min-length-one\": {\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"minLength\": 1\n\t\t}\n\t}\n}",
|
||||
"Template": "\nnode \"{{.Name}}\" {\n\tpolicy = \"write\"\n}\nservice_prefix \"\" {\n\tpolicy = \"read\"\n}"
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
Name: builtin/node
|
||||
Input variables:
|
||||
Name: String - Required - The node name.
|
||||
Example usage:
|
||||
consul acl token create -templated-policy builtin/node -var name:node-1
|
||||
Schema:
|
||||
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "$ref": "#/definitions/min-length-one" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"definitions": {
|
||||
"min-length-one": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Raw Template:
|
||||
|
||||
node "{{.Name}}" {
|
||||
policy = "write"
|
||||
}
|
||||
service_prefix "" {
|
||||
policy = "read"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
Name: builtin/node
|
||||
Input variables:
|
||||
Name: String - Required - The node name.
|
||||
Example usage:
|
||||
consul acl token create -templated-policy builtin/node -var name:node-1
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"TemplateName": "builtin/service",
|
||||
"Schema": "\n{\n\t\"type\": \"object\",\n\t\"properties\": {\n\t\t\"name\": { \"type\": \"string\", \"$ref\": \"#/definitions/min-length-one\" }\n\t},\n\t\"required\": [\"name\"],\n\t\"definitions\": {\n\t\t\"min-length-one\": {\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"minLength\": 1\n\t\t}\n\t}\n}",
|
||||
"Template": "\nservice \"{{.Name}}\" {\n\tpolicy = \"write\"\n}\nservice \"{{.Name}}-sidecar-proxy\" {\n\tpolicy = \"write\"\n}\nservice_prefix \"\" {\n\tpolicy = \"read\"\n}\nnode_prefix \"\" {\n\tpolicy = \"read\"\n}"
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
Name: builtin/service
|
||||
Input variables:
|
||||
Name: String - Required - The name of the service.
|
||||
Example usage:
|
||||
consul acl token create -templated-policy builtin/service -var name:api
|
||||
Schema:
|
||||
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "$ref": "#/definitions/min-length-one" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"definitions": {
|
||||
"min-length-one": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Raw Template:
|
||||
|
||||
service "{{.Name}}" {
|
||||
policy = "write"
|
||||
}
|
||||
service "{{.Name}}-sidecar-proxy" {
|
||||
policy = "write"
|
||||
}
|
||||
service_prefix "" {
|
||||
policy = "read"
|
||||
}
|
||||
node_prefix "" {
|
||||
policy = "read"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
Name: builtin/service
|
||||
Input variables:
|
||||
Name: String - Required - The name of the service.
|
||||
Example usage:
|
||||
consul acl token create -templated-policy builtin/service -var name:api
|
17
command/acl/templatedpolicy/testdata/FormatTemplatedPolicyList/ce/list.json.golden
vendored
Normal file
17
command/acl/templatedpolicy/testdata/FormatTemplatedPolicyList/ce/list.json.golden
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"builtin/dns": {
|
||||
"TemplateName": "builtin/dns",
|
||||
"Schema": "",
|
||||
"Template": "\nnode_prefix \"\" {\n\tpolicy = \"read\"\n}\nservice_prefix \"\" {\n\tpolicy = \"read\"\n}\nquery_prefix \"\" {\n\tpolicy = \"read\"\n}"
|
||||
},
|
||||
"builtin/node": {
|
||||
"TemplateName": "builtin/node",
|
||||
"Schema": "\n{\n\t\"type\": \"object\",\n\t\"properties\": {\n\t\t\"name\": { \"type\": \"string\", \"$ref\": \"#/definitions/min-length-one\" }\n\t},\n\t\"required\": [\"name\"],\n\t\"definitions\": {\n\t\t\"min-length-one\": {\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"minLength\": 1\n\t\t}\n\t}\n}",
|
||||
"Template": "\nnode \"{{.Name}}\" {\n\tpolicy = \"write\"\n}\nservice_prefix \"\" {\n\tpolicy = \"read\"\n}"
|
||||
},
|
||||
"builtin/service": {
|
||||
"TemplateName": "builtin/service",
|
||||
"Schema": "\n{\n\t\"type\": \"object\",\n\t\"properties\": {\n\t\t\"name\": { \"type\": \"string\", \"$ref\": \"#/definitions/min-length-one\" }\n\t},\n\t\"required\": [\"name\"],\n\t\"definitions\": {\n\t\t\"min-length-one\": {\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"minLength\": 1\n\t\t}\n\t}\n}",
|
||||
"Template": "\nservice \"{{.Name}}\" {\n\tpolicy = \"write\"\n}\nservice \"{{.Name}}-sidecar-proxy\" {\n\tpolicy = \"write\"\n}\nservice_prefix \"\" {\n\tpolicy = \"read\"\n}\nnode_prefix \"\" {\n\tpolicy = \"read\"\n}"
|
||||
}
|
||||
}
|
3
command/acl/templatedpolicy/testdata/FormatTemplatedPolicyList/ce/list.pretty.golden
vendored
Normal file
3
command/acl/templatedpolicy/testdata/FormatTemplatedPolicyList/ce/list.pretty.golden
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
builtin/dns
|
||||
builtin/node
|
||||
builtin/service
|
|
@ -36,6 +36,10 @@ import (
|
|||
aclrlist "github.com/hashicorp/consul/command/acl/role/list"
|
||||
aclrread "github.com/hashicorp/consul/command/acl/role/read"
|
||||
aclrupdate "github.com/hashicorp/consul/command/acl/role/update"
|
||||
acltp "github.com/hashicorp/consul/command/acl/templatedpolicy"
|
||||
acltplist "github.com/hashicorp/consul/command/acl/templatedpolicy/list"
|
||||
acltppreview "github.com/hashicorp/consul/command/acl/templatedpolicy/preview"
|
||||
acltpread "github.com/hashicorp/consul/command/acl/templatedpolicy/read"
|
||||
acltoken "github.com/hashicorp/consul/command/acl/token"
|
||||
acltclone "github.com/hashicorp/consul/command/acl/token/clone"
|
||||
acltcreate "github.com/hashicorp/consul/command/acl/token/create"
|
||||
|
@ -178,6 +182,10 @@ func RegisteredCommands(ui cli.Ui) map[string]mcli.CommandFactory {
|
|||
entry{"acl binding-rule read", func(ui cli.Ui) (cli.Command, error) { return aclbrread.New(ui), nil }},
|
||||
entry{"acl binding-rule update", func(ui cli.Ui) (cli.Command, error) { return aclbrupdate.New(ui), nil }},
|
||||
entry{"acl binding-rule delete", func(ui cli.Ui) (cli.Command, error) { return aclbrdelete.New(ui), nil }},
|
||||
entry{"acl templated-policy", func(cli.Ui) (cli.Command, error) { return acltp.New(), nil }},
|
||||
entry{"acl templated-policy list", func(ui cli.Ui) (cli.Command, error) { return acltplist.New(ui), nil }},
|
||||
entry{"acl templated-policy read", func(ui cli.Ui) (cli.Command, error) { return acltpread.New(ui), nil }},
|
||||
entry{"acl templated-policy preview", func(ui cli.Ui) (cli.Command, error) { return acltppreview.New(ui), nil }},
|
||||
entry{"agent", func(ui cli.Ui) (cli.Command, error) { return agent.New(ui), nil }},
|
||||
entry{"catalog", func(cli.Ui) (cli.Command, error) { return catalog.New(), nil }},
|
||||
entry{"catalog datacenters", func(ui cli.Ui) (cli.Command, error) { return catlistdc.New(ui), nil }},
|
||||
|
|
Loading…
Reference in New Issue