bf57d76ec7
The original design for workload identities and ACLs allows for operators to extend the automatic capabilities of a workload by using a specially-named policy. This has shown to be potentially unsafe because of naming collisions, so instead we'll allow operators to explicitly attach a policy to a workload identity. This changeset adds workload identity fields to ACL policy objects and threads that all the way down to the command line. It also a new secondary index to the ACL policy table on namespace and job so that claim resolution can efficiently query for related policies.
188 lines
4.3 KiB
Go
188 lines
4.3 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
type ACLBootstrapCommand struct {
|
|
Meta
|
|
}
|
|
|
|
func (c *ACLBootstrapCommand) Help() string {
|
|
helpText := `
|
|
Usage: nomad acl bootstrap [options]
|
|
|
|
Bootstrap is used to bootstrap the ACL system and get an initial token.
|
|
|
|
General Options:
|
|
|
|
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
|
|
|
Bootstrap Options:
|
|
|
|
-json
|
|
Output the bootstrap response in JSON format.
|
|
|
|
-t
|
|
Format and display the bootstrap response using a Go template.
|
|
|
|
`
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *ACLBootstrapCommand) AutocompleteFlags() complete.Flags {
|
|
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
|
complete.Flags{
|
|
"-json": complete.PredictNothing,
|
|
"-t": complete.PredictAnything,
|
|
})
|
|
}
|
|
|
|
func (c *ACLBootstrapCommand) AutocompleteArgs() complete.Predictor {
|
|
return complete.PredictNothing
|
|
}
|
|
|
|
func (c *ACLBootstrapCommand) Synopsis() string {
|
|
return "Bootstrap the ACL system for initial token"
|
|
}
|
|
|
|
func (c *ACLBootstrapCommand) Name() string { return "acl bootstrap" }
|
|
|
|
func (c *ACLBootstrapCommand) Run(args []string) int {
|
|
|
|
var (
|
|
json bool
|
|
tmpl string
|
|
file string
|
|
)
|
|
|
|
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
|
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
|
flags.BoolVar(&json, "json", false, "")
|
|
flags.StringVar(&tmpl, "t", "", "")
|
|
if err := flags.Parse(args); err != nil {
|
|
return 1
|
|
}
|
|
|
|
// Check that we got no arguments
|
|
args = flags.Args()
|
|
if l := len(args); l < 0 || l > 1 {
|
|
c.Ui.Error("This command takes up to one argument")
|
|
c.Ui.Error(commandErrorText(c))
|
|
return 1
|
|
}
|
|
|
|
var terminalToken []byte
|
|
var err error
|
|
|
|
if len(args) == 1 {
|
|
switch args[0] {
|
|
case "":
|
|
terminalToken = []byte{}
|
|
case "-":
|
|
terminalToken, err = ioutil.ReadAll(os.Stdin)
|
|
default:
|
|
file = args[0]
|
|
terminalToken, err = ioutil.ReadFile(file)
|
|
}
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error reading provided token: %v", err))
|
|
return 1
|
|
}
|
|
}
|
|
|
|
// Remove newline from the token if it was passed by stdin
|
|
boottoken := strings.TrimSuffix(string(terminalToken), "\n")
|
|
|
|
// Get the HTTP client
|
|
client, err := c.Meta.Client()
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
|
return 1
|
|
}
|
|
|
|
// Get the bootstrap token
|
|
token, _, err := client.ACLTokens().BootstrapOpts(boottoken, nil)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error bootstrapping: %s", err))
|
|
return 1
|
|
}
|
|
|
|
if json || len(tmpl) > 0 {
|
|
out, err := Format(json, tmpl, token)
|
|
if err != nil {
|
|
c.Ui.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
c.Ui.Output(out)
|
|
return 0
|
|
}
|
|
|
|
// Format the output
|
|
c.Ui.Output(formatKVACLToken(token))
|
|
return 0
|
|
}
|
|
|
|
// formatACLPolicy returns formatted policy
|
|
func formatACLPolicy(policy *api.ACLPolicy) string {
|
|
output := []string{
|
|
fmt.Sprintf("Name|%s", policy.Name),
|
|
fmt.Sprintf("Description|%s", policy.Description),
|
|
fmt.Sprintf("CreateIndex|%v", policy.CreateIndex),
|
|
fmt.Sprintf("ModifyIndex|%v", policy.ModifyIndex),
|
|
}
|
|
|
|
formattedOut := formatKV(output)
|
|
|
|
if policy.JobACL != nil {
|
|
output := []string{
|
|
fmt.Sprintf("Namespace|%v", policy.JobACL.Namespace),
|
|
fmt.Sprintf("JobID|%v", policy.JobACL.JobID),
|
|
fmt.Sprintf("Group|%v", policy.JobACL.Group),
|
|
fmt.Sprintf("Task|%v", policy.JobACL.Task),
|
|
}
|
|
formattedOut += "\n\n[bold]Associated Workload[reset]\n"
|
|
formattedOut += formatKV(output)
|
|
}
|
|
|
|
// these are potentially large blobs so leave till the end
|
|
formattedOut += "\n\n[bold]Rules[reset]\n\n"
|
|
formattedOut += policy.Rules
|
|
|
|
return formattedOut
|
|
}
|
|
|
|
// formatKVACLToken returns a K/V formatted ACL token
|
|
func formatKVACLToken(token *api.ACLToken) string {
|
|
// Add the fixed preamble
|
|
output := []string{
|
|
fmt.Sprintf("Accessor ID|%s", token.AccessorID),
|
|
fmt.Sprintf("Secret ID|%s", token.SecretID),
|
|
fmt.Sprintf("Name|%s", token.Name),
|
|
fmt.Sprintf("Type|%s", token.Type),
|
|
fmt.Sprintf("Global|%v", token.Global),
|
|
}
|
|
|
|
// Special case the policy output
|
|
if token.Type == "management" {
|
|
output = append(output, "Policies|n/a")
|
|
} else {
|
|
output = append(output, fmt.Sprintf("Policies|%v", token.Policies))
|
|
}
|
|
|
|
// Add the generic output
|
|
output = append(output,
|
|
fmt.Sprintf("Create Time|%v", token.CreateTime),
|
|
fmt.Sprintf("Create Index|%d", token.CreateIndex),
|
|
fmt.Sprintf("Modify Index|%d", token.ModifyIndex),
|
|
)
|
|
return formatKV(output)
|
|
}
|