2023-03-28 19:12:30 +00:00
// Copyright (c) HashiCorp, Inc.
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-28 19:12:30 +00:00
2019-04-15 20:43:19 +00:00
package rolecreate
import (
"flag"
"fmt"
2020-02-15 15:06:05 +00:00
"strings"
2019-04-15 20:43:19 +00:00
2022-04-05 21:10:06 +00:00
"github.com/mitchellh/cli"
2019-04-15 20:43:19 +00:00
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/acl"
2020-02-15 15:06:05 +00:00
"github.com/hashicorp/consul/command/acl/role"
2019-04-15 20:43:19 +00:00
"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
2023-09-08 12:45:24 +00:00
name string
description string
policyIDs [ ] string
policyNames [ ] string
serviceIdents [ ] string
nodeIdents [ ] string
templatedPolicy string
templatedPolicyFile string
templatedPolicyVariables [ ] string
2019-04-15 20:43:19 +00:00
showMeta bool
2020-02-15 15:06:05 +00:00
format string
2019-04-15 20:43:19 +00:00
}
func ( c * cmd ) init ( ) {
c . flags = flag . NewFlagSet ( "" , flag . ContinueOnError )
c . flags . BoolVar ( & c . showMeta , "meta" , false , "Indicates that role metadata such " +
"as the content hash and raft indices should be shown for each entry" )
c . flags . StringVar ( & c . name , "name" , "" , "The new role's name. This flag is required." )
c . flags . StringVar ( & c . description , "description" , "" , "A description of the role" )
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . policyIDs ) , "policy-id" , "ID of a " +
"policy to use for this role. May be specified multiple times" )
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . policyNames ) , "policy-name" , "Name of a " +
"policy to use for this role. May be specified multiple times" )
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . serviceIdents ) , "service-identity" , "Name of a " +
"service identity to use for this role. May be specified multiple times. Format is " +
"the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,..." )
2020-06-16 16:54:27 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . nodeIdents ) , "node-identity" , "Name of a " +
"node identity to use for this role. May be specified multiple times. Format is " +
"NODENAME:DATACENTER" )
2023-09-08 12:45:24 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . templatedPolicyVariables ) , "var" , "Templated policy variables." +
" Must be used in combination with -templated-policy flag to specify required variables." +
" May be specified multiple times with different variables." +
" Format is VariableName:Value" )
c . flags . StringVar ( & c . templatedPolicy , "templated-policy" , "" , "The templated policy name. Use -var flag to specify variables when required." )
c . flags . StringVar ( & c . templatedPolicyFile , "templated-policy-file" , "" , "Path to a file containing templated policy names and variables." )
2020-02-15 15:06:05 +00:00
c . flags . StringVar (
& c . format ,
"format" ,
role . PrettyFormat ,
fmt . Sprintf ( "Output format {%s}" , strings . Join ( role . GetSupportedFormats ( ) , "|" ) ) ,
)
2019-04-15 20:43:19 +00:00
c . http = & flags . HTTPFlags { }
flags . Merge ( c . flags , c . http . ClientFlags ( ) )
flags . Merge ( c . flags , c . http . ServerFlags ( ) )
2021-07-21 19:45:24 +00:00
flags . Merge ( c . flags , c . http . MultiTenancyFlags ( ) )
2019-04-15 20:43:19 +00:00
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 . name == "" {
2023-09-08 12:45:24 +00:00
c . UI . Error ( "Missing required '-name' flag" )
2019-04-15 20:43:19 +00:00
c . UI . Error ( c . Help ( ) )
return 1
}
2023-09-08 12:45:24 +00:00
if len ( c . policyNames ) == 0 && len ( c . policyIDs ) == 0 && len ( c . serviceIdents ) == 0 && len ( c . nodeIdents ) == 0 &&
len ( c . templatedPolicy ) == 0 && len ( c . templatedPolicyFile ) == 0 {
c . UI . Error ( "Cannot create a role without specifying -policy-name, -policy-id, -service-identity, -node-identity, -templated-policy-file or -templated-policy at least once" )
2019-04-15 20:43:19 +00:00
return 1
}
2023-10-26 18:15:12 +00:00
if len ( c . templatedPolicyFile ) != 0 && len ( c . templatedPolicy ) != 0 {
c . UI . Error ( "Cannot combine the use of templated-policy flag with templated-policy-file. " +
"To create a role with a single templated policy and simple use case, use -templated-policy. " +
"For multiple templated policies and more complicated use cases, use -templated-policy-file" )
return 1
}
2019-04-15 20:43:19 +00:00
client , err := c . http . APIClient ( )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error connecting to Consul agent: %s" , err ) )
return 1
}
newRole := & api . ACLRole {
Name : c . name ,
Description : c . description ,
}
for _ , policyName := range c . policyNames {
// We could resolve names to IDs here but there isn't any reason why its would be better
// than allowing the agent to do it.
newRole . Policies = append ( newRole . Policies , & api . ACLRolePolicyLink { Name : policyName } )
}
for _ , policyID := range c . policyIDs {
policyID , err := acl . GetPolicyIDFromPartial ( client , policyID )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error resolving policy ID %s: %v" , policyID , err ) )
return 1
}
newRole . Policies = append ( newRole . Policies , & api . ACLRolePolicyLink { ID : policyID } )
}
parsedServiceIdents , err := acl . ExtractServiceIdentities ( c . serviceIdents )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
newRole . ServiceIdentities = parsedServiceIdents
2020-06-16 16:54:27 +00:00
parsedNodeIdents , err := acl . ExtractNodeIdentities ( c . nodeIdents )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
newRole . NodeIdentities = parsedNodeIdents
2023-09-08 12:45:24 +00:00
parsedTemplatedPolicies , err := acl . ExtractTemplatedPolicies ( c . templatedPolicy , c . templatedPolicyFile , c . templatedPolicyVariables )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
newRole . TemplatedPolicies = parsedTemplatedPolicies
2020-02-15 15:06:05 +00:00
r , _ , err := client . ACL ( ) . RoleCreate ( newRole , nil )
2019-04-15 20:43:19 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Failed to create new role: %v" , err ) )
return 1
}
2020-02-15 15:06:05 +00:00
formatter , err := role . NewFormatter ( c . format , c . showMeta )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
out , err := formatter . FormatRole ( r )
if err != nil {
c . UI . Error ( err . Error ( ) )
2020-03-26 15:24:21 +00:00
return 1
2020-02-15 15:06:05 +00:00
}
if out != "" {
c . UI . Info ( out )
}
2019-04-15 20:43:19 +00:00
return 0
}
func ( c * cmd ) Synopsis ( ) string {
return synopsis
}
func ( c * cmd ) Help ( ) string {
return flags . Usage ( c . help , nil )
}
2019-05-01 21:11:23 +00:00
const synopsis = "Create an ACL role"
2019-04-15 20:43:19 +00:00
const help = `
Usage : consul acl role create - name NAME [ options ]
Create a new role :
$ consul acl role create - name "new-role" \
- description "This is an example role" \
- policy - id b52fc3de - 5 \
- policy - name "acl-replication" \
- service - identity "web" \
2023-09-08 12:45:24 +00:00
- service - identity "db:east,west" \
- templated - policy "builtin/service" \
- var "name:api"
2019-04-15 20:43:19 +00:00
`