consul/command/acl/token/formatter.go

430 lines
16 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
package token
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
)
const (
PrettyFormat string = "pretty"
JSONFormat string = "json"
WHITESPACE_2 string = "\t"
WHITESPACE_4 string = "\t\t"
)
// Formatter defines methods provided by token command output formatter
type Formatter interface {
FormatToken(token *api.ACLToken) (string, error)
FormatTokenExpanded(token *api.ACLTokenExpanded) (string, error)
FormatTokenList(tokens []*api.ACLTokenListEntry) (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: %s", format)
}
return formatter, err
}
func newPrettyFormatter(showMeta bool) Formatter {
return &prettyFormatter{showMeta}
}
type prettyFormatter struct {
showMeta bool
}
func (f *prettyFormatter) FormatToken(token *api.ACLToken) (string, error) {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID))
buffer.WriteString(fmt.Sprintf("SecretID: %s\n", token.SecretID))
if token.Partition != "" {
buffer.WriteString(fmt.Sprintf("Partition: %s\n", token.Partition))
}
if token.Namespace != "" {
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace))
}
buffer.WriteString(fmt.Sprintf("Description: %s\n", token.Description))
buffer.WriteString(fmt.Sprintf("Local: %t\n", token.Local))
if token.AuthMethod != "" {
buffer.WriteString(fmt.Sprintf("Auth Method: %s (Namespace: %s)\n", token.AuthMethod, token.AuthMethodNamespace))
}
buffer.WriteString(fmt.Sprintf("Create Time: %v\n", token.CreateTime))
if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() {
buffer.WriteString(fmt.Sprintf("Expiration Time: %v\n", *token.ExpirationTime))
}
if f.showMeta {
buffer.WriteString(fmt.Sprintf("Hash: %x\n", token.Hash))
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", token.CreateIndex))
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", token.ModifyIndex))
}
if len(token.Policies) > 0 {
buffer.WriteString(fmt.Sprintln("Policies:"))
for _, policy := range token.Policies {
buffer.WriteString(fmt.Sprintf(" %s - %s\n", policy.ID, policy.Name))
}
}
if len(token.Roles) > 0 {
buffer.WriteString(fmt.Sprintln("Roles:"))
for _, role := range token.Roles {
buffer.WriteString(fmt.Sprintf(" %s - %s\n", role.ID, role.Name))
}
}
if len(token.ServiceIdentities) > 0 {
buffer.WriteString(fmt.Sprintln("Service Identities:"))
for _, svcid := range token.ServiceIdentities {
if len(svcid.Datacenters) > 0 {
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: %s)\n", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
} else {
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: all)\n", svcid.ServiceName))
}
}
}
if len(token.NodeIdentities) > 0 {
buffer.WriteString(fmt.Sprintln("Node Identities:"))
for _, nodeid := range token.NodeIdentities {
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
}
}
if len(token.TemplatedPolicies) > 0 {
buffer.WriteString(fmt.Sprintln("Templated Policies:"))
for _, templatedPolicy := range token.TemplatedPolicies {
buffer.WriteString(fmt.Sprintf(" %s\n", templatedPolicy.TemplateName))
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
buffer.WriteString(fmt.Sprintf(" Name: %s\n", templatedPolicy.TemplateVariables.Name))
}
if len(templatedPolicy.Datacenters) > 0 {
buffer.WriteString(fmt.Sprintf(" Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
} else {
buffer.WriteString(" Datacenters: all\n")
}
}
}
return buffer.String(), nil
}
func (f *prettyFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (string, error) {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID))
buffer.WriteString(fmt.Sprintf("SecretID: %s\n", token.SecretID))
if token.Partition != "" {
buffer.WriteString(fmt.Sprintf("Partition: %s\n", token.Partition))
}
if token.Namespace != "" {
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace))
}
buffer.WriteString(fmt.Sprintf("Description: %s\n", token.Description))
buffer.WriteString(fmt.Sprintf("Local: %t\n", token.Local))
if token.AuthMethod != "" {
buffer.WriteString(fmt.Sprintf("Auth Method: %s (Namespace: %s)\n", token.AuthMethod, token.AuthMethodNamespace))
}
buffer.WriteString(fmt.Sprintf("Create Time: %v\n", token.CreateTime))
if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() {
buffer.WriteString(fmt.Sprintf("Expiration Time: %v\n", *token.ExpirationTime))
}
if f.showMeta {
buffer.WriteString(fmt.Sprintf("Hash: %x\n", token.Hash))
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", token.CreateIndex))
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", token.ModifyIndex))
}
policies := make(map[string]api.ACLPolicy)
roles := make(map[string]api.ACLRole)
for _, policy := range token.ExpandedPolicies {
policies[policy.ID] = policy
}
for _, role := range token.ExpandedRoles {
roles[role.ID] = role
}
formatPolicy := func(policy api.ACLPolicy, indent string) {
buffer.WriteString(fmt.Sprintf(indent+"Policy Name: %s\n", policy.Name))
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"ID: %s\n", policy.ID))
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description))
buffer.WriteString(indent + WHITESPACE_2 + "Rules:\n")
buffer.WriteString(indent + WHITESPACE_4)
buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4))
buffer.WriteString("\n\n")
}
if len(token.ACLToken.Policies) > 0 {
buffer.WriteString("Policies:\n")
for _, policyLink := range token.ACLToken.Policies {
formatPolicy(policies[policyLink.ID], WHITESPACE_2)
}
}
entMeta := acl.NewEnterpriseMetaWithPartition(token.Partition, token.Namespace)
formatServiceIdentity := func(svcIdentity *api.ACLServiceIdentity, indent string) {
if len(svcIdentity.Datacenters) > 0 {
buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenters: %s)\n", svcIdentity.ServiceName, strings.Join(svcIdentity.Datacenters, ", ")))
} else {
buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenters: all)\n", svcIdentity.ServiceName))
}
identity := structs.ACLServiceIdentity{ServiceName: svcIdentity.ServiceName, Datacenters: svcIdentity.Datacenters}
policy := identity.SyntheticPolicy(&entMeta)
displaySyntheticPolicy(policy, &buffer, indent)
}
if len(token.ACLToken.ServiceIdentities) > 0 {
buffer.WriteString("Service Identities:\n")
for _, svcIdentity := range token.ACLToken.ServiceIdentities {
formatServiceIdentity(svcIdentity, WHITESPACE_2)
}
}
formatNodeIdentity := func(nodeIdentity *api.ACLNodeIdentity, indent string) {
buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenter: %s)\n", nodeIdentity.NodeName, nodeIdentity.Datacenter))
identity := structs.ACLNodeIdentity{NodeName: nodeIdentity.NodeName, Datacenter: nodeIdentity.Datacenter}
policy := identity.SyntheticPolicy(&entMeta)
displaySyntheticPolicy(policy, &buffer, indent)
}
if len(token.ACLToken.NodeIdentities) > 0 {
buffer.WriteString("Node Identities:\n")
for _, nodeIdentity := range token.ACLToken.NodeIdentities {
formatNodeIdentity(nodeIdentity, WHITESPACE_2)
}
}
formatTemplatedPolicy := func(templatedPolicy *api.ACLTemplatedPolicy, indent string) {
buffer.WriteString(fmt.Sprintf(indent+"%s\n", templatedPolicy.TemplateName))
tp := structs.ACLTemplatedPolicy{
TemplateName: templatedPolicy.TemplateName,
Datacenters: templatedPolicy.Datacenters,
}
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
tp.TemplateVariables = &structs.ACLTemplatedPolicyVariables{
Name: templatedPolicy.TemplateVariables.Name,
}
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Name: %s\n", templatedPolicy.TemplateVariables.Name))
}
if len(templatedPolicy.Datacenters) > 0 {
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
} else {
buffer.WriteString(fmt.Sprintf(indent + WHITESPACE_2 + "Datacenters: all\n"))
}
policy, _ := tp.SyntheticPolicy(&entMeta)
displaySyntheticPolicy(policy, &buffer, indent)
}
if len(token.ACLToken.TemplatedPolicies) > 0 {
buffer.WriteString("Templated Policies:\n")
for _, templatedPolicy := range token.ACLToken.TemplatedPolicies {
formatTemplatedPolicy(templatedPolicy, WHITESPACE_2)
}
}
formatRole := func(role api.ACLRole, indent string) {
buffer.WriteString(fmt.Sprintf(indent+"Role Name: %s\n", role.Name))
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"ID: %s\n", role.ID))
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", role.Description))
if len(role.Policies) > 0 {
buffer.WriteString(indent + WHITESPACE_2 + "Policies:\n")
for _, policyLink := range role.Policies {
formatPolicy(policies[policyLink.ID], indent+WHITESPACE_4)
}
}
if len(role.ServiceIdentities) > 0 {
buffer.WriteString(indent + WHITESPACE_2 + "Service Identities:\n")
for _, svcIdentity := range role.ServiceIdentities {
formatServiceIdentity(svcIdentity, indent+WHITESPACE_4)
}
}
if len(role.NodeIdentities) > 0 {
buffer.WriteString(indent + WHITESPACE_2 + "Node Identities:\n")
for _, nodeIdentity := range role.NodeIdentities {
formatNodeIdentity(nodeIdentity, indent+WHITESPACE_4)
}
}
}
if len(token.ACLToken.Roles) > 0 {
buffer.WriteString("Roles:\n")
for _, roleLink := range token.ACLToken.Roles {
role := roles[roleLink.ID]
formatRole(role, WHITESPACE_2)
}
}
buffer.WriteString("=== End of Authorizer Layer 0: Token ===\n")
if len(token.NamespaceDefaultPolicyIDs) > 0 || len(token.NamespaceDefaultRoleIDs) > 0 {
buffer.WriteString("=== Start of Authorizer Layer 1: Token Namespaces Defaults (Inherited) ===\n")
buffer.WriteString(fmt.Sprintf("Description: ACL Roles inherited by all Tokens in Namespace %q\n\n", token.Namespace))
buffer.WriteString("Namespace Policy Defaults:\n")
for _, policyID := range token.NamespaceDefaultPolicyIDs {
formatPolicy(policies[policyID], WHITESPACE_2)
}
buffer.WriteString("Namespace Role Defaults:\n")
for _, roleID := range token.NamespaceDefaultRoleIDs {
formatRole(roles[roleID], WHITESPACE_2)
}
buffer.WriteString("=== End of Authorizer Layer 1: Token Namespaces Defaults (Inherited) ===\n")
}
buffer.WriteString("=== Start of Authorizer Layer 2: Agent Configuration Defaults (Inherited) ===\n")
buffer.WriteString("Description: Defined at request-time by the agent that resolves the ACL token; other agents may have different configuration defaults\n")
buffer.WriteString(fmt.Sprintf("Resolved By Agent: %q\n\n", token.ResolvedByAgent))
buffer.WriteString(fmt.Sprintf("Default Policy: %s\n", token.AgentACLDefaultPolicy))
buffer.WriteString(WHITESPACE_2 + "Description: Backstop rule used if no preceding layer has a matching rule (refer to default_policy option in agent configuration)\n\n")
buffer.WriteString(fmt.Sprintf("Down Policy: %s\n", token.AgentACLDownPolicy))
buffer.WriteString(WHITESPACE_2 + "Description: Defines what to do if this Token's information cannot be read from the primary_datacenter (refer to down_policy option in agent configuration)\n\n")
return buffer.String(), nil
}
func displaySyntheticPolicy(policy *structs.ACLPolicy, buffer *bytes.Buffer, indent string) {
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description))
buffer.WriteString(indent + WHITESPACE_2 + "Rules:")
buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4))
buffer.WriteString("\n\n")
}
func (f *prettyFormatter) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) {
var buffer bytes.Buffer
first := true
for _, token := range tokens {
if first {
first = false
} else {
buffer.WriteString("\n")
}
buffer.WriteString(f.formatTokenListEntry(token))
}
return buffer.String(), nil
}
func (f *prettyFormatter) formatTokenListEntry(token *api.ACLTokenListEntry) string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID))
buffer.WriteString(fmt.Sprintf("SecretID: %s\n", token.SecretID))
if token.Partition != "" {
buffer.WriteString(fmt.Sprintf("Partition: %s\n", token.Partition))
}
if token.Namespace != "" {
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace))
}
buffer.WriteString(fmt.Sprintf("Description: %s\n", token.Description))
buffer.WriteString(fmt.Sprintf("Local: %t\n", token.Local))
if token.AuthMethod != "" {
buffer.WriteString(fmt.Sprintf("Auth Method: %s (Namespace: %s)\n", token.AuthMethod, token.AuthMethodNamespace))
}
buffer.WriteString(fmt.Sprintf("Create Time: %v\n", token.CreateTime))
if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() {
buffer.WriteString(fmt.Sprintf("Expiration Time: %v\n", *token.ExpirationTime))
}
if f.showMeta {
buffer.WriteString(fmt.Sprintf("Hash: %x\n", token.Hash))
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", token.CreateIndex))
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", token.ModifyIndex))
}
if len(token.Policies) > 0 {
buffer.WriteString(fmt.Sprintln("Policies:"))
for _, policy := range token.Policies {
buffer.WriteString(fmt.Sprintf(" %s - %s\n", policy.ID, policy.Name))
}
}
if len(token.Roles) > 0 {
buffer.WriteString(fmt.Sprintln("Roles:"))
for _, role := range token.Roles {
buffer.WriteString(fmt.Sprintf(" %s - %s\n", role.ID, role.Name))
}
}
if len(token.ServiceIdentities) > 0 {
buffer.WriteString(fmt.Sprintln("Service Identities:"))
for _, svcid := range token.ServiceIdentities {
if len(svcid.Datacenters) > 0 {
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: %s)\n", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
} else {
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: all)\n", svcid.ServiceName))
}
}
}
if len(token.NodeIdentities) > 0 {
buffer.WriteString(fmt.Sprintln("Node Identities:"))
for _, nodeid := range token.NodeIdentities {
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
}
}
if len(token.TemplatedPolicies) > 0 {
buffer.WriteString(fmt.Sprintln("Templated Policies:"))
for _, templatedPolicy := range token.TemplatedPolicies {
buffer.WriteString(fmt.Sprintf(" %s\n", templatedPolicy.TemplateName))
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
buffer.WriteString(fmt.Sprintf(" Name: %s\n", templatedPolicy.TemplateVariables.Name))
}
if len(templatedPolicy.Datacenters) > 0 {
buffer.WriteString(fmt.Sprintf(" Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
} else {
buffer.WriteString(" Datacenters: all\n")
}
}
}
return buffer.String()
}
func newJSONFormatter(showMeta bool) Formatter {
return &jsonFormatter{showMeta}
}
type jsonFormatter struct {
showMeta bool
}
func (f *jsonFormatter) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) {
b, err := json.MarshalIndent(tokens, "", " ")
if err != nil {
return "", fmt.Errorf("Failed to marshal tokens: %v", err)
}
return string(b), nil
}
func (f *jsonFormatter) FormatToken(token *api.ACLToken) (string, error) {
b, err := json.MarshalIndent(token, "", " ")
if err != nil {
return "", fmt.Errorf("Failed to marshal token: %v", err)
}
return string(b), nil
}
func (f *jsonFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (string, error) {
b, err := json.MarshalIndent(token, "", " ")
if err != nil {
return "", fmt.Errorf("Failed to marshal token: %v", err)
}
return string(b), nil
}