Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions cmd/iam/iam_role_assume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package iam

import (
"errors"
"github.com/spf13/cobra"

exocmd "github.com/exoscale/cli/cmd"
"github.com/exoscale/cli/pkg/account"
"github.com/exoscale/cli/pkg/globalstate"
"github.com/exoscale/cli/pkg/output"
v3 "github.com/exoscale/egoscale/v3"
)

type iamRoleAssumeOutput struct {
Key string `json:"key"`
Name string `json:"name"`
OrgID string `json:"org-id"`
RoleID string `json:"role-id"`
Secret string `json:"secret"`
}

func (o *iamRoleAssumeOutput) ToJSON() { output.JSON(o) }
func (o *iamRoleAssumeOutput) ToText() { output.Text(o) }
func (o *iamRoleAssumeOutput) ToTable() { output.Table(o) }

type iamRoleAssumeCmd struct {
exocmd.CliCommandSettings `cli-cmd:"-"`

Role string `cli-arg:"#" cli-usage:"ID|NAME"`

Ttl int64 `cli-flag:"ttl" cli-usage:"Time To Live for the requested key in seconds (default: 300)"`

_ bool `cli-cmd:"assume"`
}

func (c *iamRoleAssumeCmd) CmdAliases() []string { return nil }

func (c *iamRoleAssumeCmd) CmdShort() string {
return "Request generation of key/secret allowing calls as of target role"
}

func (c *iamRoleAssumeCmd) CmdLong() string {
return "Request generation of key/secret allowing calls as of target role"
}

func (c *iamRoleAssumeCmd) CmdPreRun(cmd *cobra.Command, args []string) error {
return exocmd.CliCommandDefaultPreRun(c, cmd, args)
}

func (c *iamRoleAssumeCmd) CmdRun(cmd *cobra.Command, _ []string) error {
if c.Role == "" {
return errors.New("role not provided")
}

ctx := exocmd.GContext
client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, v3.ZoneName(account.CurrentAccount.DefaultZone))
if err != nil {
return err
}

assumeRoleRq := v3.AssumeIAMRoleRequest{}

if cmd.Flags().Changed(exocmd.MustCLICommandFlagName(c, &c.Ttl)) {
assumeRoleRq.Ttl = c.Ttl
}

apiKey, err := client.AssumeIAMRole(ctx, v3.UUID(c.Role), assumeRoleRq)
if err != nil {
return err
}

out := iamRoleAssumeOutput{
Key: apiKey.Key,
Name: apiKey.Name,
OrgID: apiKey.OrgID,
RoleID: apiKey.RoleID,
Secret: apiKey.Secret,
}

return c.OutputFunc(&out, nil)
}

func init() {
cobra.CheckErr(exocmd.RegisterCLICommand(iamRoleCmd, &iamRoleAssumeCmd{
CliCommandSettings: exocmd.DefaultCLICmdSettings(),
}))
}
54 changes: 41 additions & 13 deletions cmd/iam/iam_role_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ type iamRoleCreateCmd struct {

_ bool `cli-cmd:"create"`

Name string `cli-arg:"#" cli-usage:"NAME"`
Description string `cli-flag:"description" cli-usage:"Role description"`
Permissions []string `cli-flag:"permissions" cli-usage:"Role permissions"`
Editable bool `cli-flag:"editable" cli-usage:"Set --editable=false do prevent editing Policy after creation"`
Labels map[string]string `cli-flag:"label" cli-usage:"Role labels (format: key=value)"`
Policy string `cli-flag:"policy" cli-usage:"Role policy (use '-' to read from STDIN)"`
Name string `cli-arg:"#" cli-usage:"NAME"`
Description string `cli-flag:"description" cli-usage:"Role description"`
Permissions []string `cli-flag:"permissions" cli-usage:"Role permissions"`
Editable bool `cli-flag:"editable" cli-usage:"Set --editable=false do prevent editing Policy after creation"`
Labels map[string]string `cli-flag:"label" cli-usage:"Role labels (format: key=value)"`
Policy string `cli-flag:"policy" cli-usage:"Role policy (use '-' to read from STDIN)"`
AssumeRolePolicy string `cli-flag:"assume-role-policy" cli-usage:"Assume Role policy (use '-' to read from STDIN)"`
MaxSessionTtl int64 `cli-flag:"max-session-ttl" cli-usage:"Maximum TTL requester is allowed to ask for when assuming a role (0 implies default)"`
}

func (c *iamRoleCreateCmd) CmdAliases() []string { return nil }
Expand All @@ -37,9 +39,9 @@ func (c *iamRoleCreateCmd) CmdShort() string {

func (c *iamRoleCreateCmd) CmdLong() string {
return fmt.Sprintf(`This command creates a new IAM Role.
To read the Policy from STDIN, append '-' to the '--policy' flag.
To read a policy from STDIN, append '-' to the '--policy' or '--assume-role-policy' flag.

Pro Tip: you can reuse an existing role policy by providing the output of the show command as input:
Pro Tip: you can reuse an existing policy by providing the output of the show command as input:

exo iam role show --policy --output-format json <role-name> | exo iam role create <new-role-name> --policy -

Expand All @@ -63,6 +65,7 @@ func (c *iamRoleCreateCmd) CmdRun(cmd *cobra.Command, _ []string) error {
}

var policy *v3.IAMPolicy
var assumeRolePolicy *v3.IAMPolicy

// Policy is optional, if not set API will default to `allow all`
if c.Policy != "" {
Expand All @@ -85,18 +88,43 @@ func (c *iamRoleCreateCmd) CmdRun(cmd *cobra.Command, _ []string) error {
}
}

if c.AssumeRolePolicy != "" {
// If Assume Role Policy value is `-` read from STDIN
if c.AssumeRolePolicy == "-" {
inputReader := cmd.InOrStdin()
b, err := io.ReadAll(inputReader)
if err != nil {
return fmt.Errorf("failed to read assume role policy from stdin: %w", err)
}

c.AssumeRolePolicy = string(b)
}

var err error

assumeRolePolicy, err = iamPolicyFromJSON([]byte(c.AssumeRolePolicy))
if err != nil {
return fmt.Errorf("failed to parse IAM policy: %w", err)
}
}

role := v3.CreateIAMRoleRequest{
Name: c.Name,
Editable: &c.Editable,
Labels: c.Labels,
Permissions: c.Permissions,
Policy: policy,
Name: c.Name,
Editable: &c.Editable,
Labels: c.Labels,
Permissions: c.Permissions,
Policy: policy,
AssumeRolePolicy: assumeRolePolicy,
}

if c.Description != "" {
role.Description = c.Description
}

if c.MaxSessionTtl != 0 {
role.MaxSessionTtl = c.MaxSessionTtl
}

op, err := client.CreateIAMRole(ctx, role)
if err != nil {
return err
Expand Down
62 changes: 49 additions & 13 deletions cmd/iam/iam_role_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import (
)

type iamRoleShowOutput struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Editable bool `json:"editable"`
Labels map[string]string `json:"labels"`
Permissions []string `json:"permission"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Editable bool `json:"editable"`
Labels map[string]string `json:"labels"`
Permissions []string `json:"permission"`
MaxSessionTtl int64 `json:"max-session-ttl"`
}

func (o *iamRoleShowOutput) ToJSON() { output.JSON(o) }
Expand All @@ -33,7 +34,8 @@ type iamRoleShowCmd struct {

_ bool `cli-cmd:"show"`

Policy bool `cli-flag:"policy" cli-usage:"Print IAM Role policy"`
Policy bool `cli-flag:"policy" cli-usage:"Print IAM Role policy"`
AssumeRolePolicy bool `cli-flag:"assume-role-policy" cli-usage:"Print IAM Assume Role policy"`

Role string `cli-arg:"#" cli-usage:"ID|NAME"`
}
Expand Down Expand Up @@ -104,13 +106,47 @@ func (c *iamRoleShowCmd) CmdRun(_ *cobra.Command, _ []string) error {
return c.OutputFunc(&out, nil)
}

if c.AssumeRolePolicy {

if role.AssumeRolePolicy == nil {
return nil
}

policy := role.AssumeRolePolicy

out := iamPolicyOutput{
DefaultServiceStrategy: string(policy.DefaultServiceStrategy),
Services: map[string]iamPolicyServiceOutput{},
}

for name, service := range policy.Services {
rules := []iamPolicyServiceRuleOutput{}
if service.Type == "rules" {
for _, rule := range service.Rules {
rules = append(rules, iamPolicyServiceRuleOutput{
Action: string(rule.Action),
Expression: rule.Expression,
})
}
}

out.Services[name] = iamPolicyServiceOutput{
Type: string(service.Type),
Rules: rules,
}
}

return c.OutputFunc(&out, nil)
}

out := iamRoleShowOutput{
ID: role.ID.String(),
Description: role.Description,
Editable: utils.DefaultBool(role.Editable, false),
Labels: role.Labels,
Name: role.Name,
Permissions: role.Permissions,
ID: role.ID.String(),
Description: role.Description,
Editable: utils.DefaultBool(role.Editable, false),
Labels: role.Labels,
Name: role.Name,
Permissions: role.Permissions,
MaxSessionTtl: role.MaxSessionTtl,
}

return c.OutputFunc(&out, nil)
Expand Down
89 changes: 56 additions & 33 deletions cmd/iam/iam_role_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ type iamRoleUpdateCmd struct {

Role string `cli-arg:"#" cli-usage:"ID|NAME"`

Description string `cli-flag:"description" cli-usage:"Role description"`
Permissions []string `cli-flag:"permissions" cli-usage:"Role permissions"`
Labels map[string]string `cli-flag:"label" cli-usage:"Role labels (format: key=value)"`
Policy string `cli-flag:"policy" cli-usage:"Role policy (use '-' to read from STDIN)"`
Description string `cli-flag:"description" cli-usage:"Role description"`
Permissions []string `cli-flag:"permissions" cli-usage:"Role permissions"`
Labels map[string]string `cli-flag:"label" cli-usage:"Role labels (format: key=value)"`
Policy string `cli-flag:"policy" cli-usage:"Role policy (use '-' to read from STDIN)"`
AssumeRolePolicy string `cli-flag:"assume-role-policy" cli-usage:"Assume Role policy (use '-' to read from STDIN)"`
MaxSessionTtl int64 `cli-flag:"max-session-ttl" cli-usage:"Maximum TTL requester is allowed to ask for when assuming a role"`

_ bool `cli-cmd:"update"`
}
Expand All @@ -37,7 +39,7 @@ func (c *iamRoleUpdateCmd) CmdShort() string {

func (c *iamRoleUpdateCmd) CmdLong() string {
return fmt.Sprintf(`This command updates an IAM Role.
When you supply '-' as a flag argument to '--policy', the new policy will be read from STDIN.
To read a policy from STDIN, append '-' to the '--policy' or '--assume-role-policy' flag.

Supported output template annotations: %s`,
strings.Join(output.TemplateAnnotations(&iamPolicyOutput{}), ", "))
Expand Down Expand Up @@ -78,6 +80,9 @@ func (c *iamRoleUpdateCmd) CmdRun(cmd *cobra.Command, _ []string) error {
if cmd.Flags().Changed(exocmd.MustCLICommandFlagName(c, &c.Permissions)) {
updateRole.Permissions = c.Permissions
}
if cmd.Flags().Changed(exocmd.MustCLICommandFlagName(c, &c.MaxSessionTtl)) {
updateRole.MaxSessionTtl = c.MaxSessionTtl
}

op, err := client.UpdateIAMRole(ctx, role.ID, updateRole)
if err != nil {
Expand All @@ -90,42 +95,60 @@ func (c *iamRoleUpdateCmd) CmdRun(cmd *cobra.Command, _ []string) error {
return err
}

// If we don't need to update Policy we can exit now
if c.Policy == "" {
if !globalstate.Quiet {
return (&iamRoleShowCmd{
CliCommandSettings: c.CliCommandSettings,
Role: role.ID.String(),
}).CmdRun(nil, nil)
}
if c.Policy != "" {
if c.Policy == "-" {
inputReader := cmd.InOrStdin()
b, err := io.ReadAll(inputReader)
if err != nil {
return fmt.Errorf("failed to read policy from stdin: %w", err)
}

return nil
}
c.Policy = string(b)
}

if c.Policy == "-" {
inputReader := cmd.InOrStdin()
b, err := io.ReadAll(inputReader)
policy, err := iamPolicyFromJSON([]byte(c.Policy))
if err != nil {
return fmt.Errorf("failed to read policy from stdin: %w", err)
return fmt.Errorf("failed to parse IAM policy: %w", err)
}

c.Policy = string(b)
op, err = client.UpdateIAMRolePolicy(ctx, role.ID, *policy)
if err != nil {
return err
}
utils.DecorateAsyncOperation("Updating IAM role policy...", func() {
_, err = client.Wait(ctx, op, v3.OperationStateSuccess)
})
if err != nil {
return err
}
}

policy, err := iamPolicyFromJSON([]byte(c.Policy))
if err != nil {
return fmt.Errorf("failed to parse IAM policy: %w", err)
}
if c.AssumeRolePolicy != "" {
if c.AssumeRolePolicy == "-" {
inputReader := cmd.InOrStdin()
b, err := io.ReadAll(inputReader)
if err != nil {
return fmt.Errorf("failed to read assume role policy from stdin: %w", err)
}

op, err = client.UpdateIAMRolePolicy(ctx, role.ID, *policy)
if err != nil {
return err
}
utils.DecorateAsyncOperation("Updating IAM role policy...", func() {
_, err = client.Wait(ctx, op, v3.OperationStateSuccess)
})
if err != nil {
return err
c.AssumeRolePolicy = string(b)
}

assumeRolePolicy, err := iamPolicyFromJSON([]byte(c.AssumeRolePolicy))
if err != nil {
return fmt.Errorf("failed to parse IAM assume role policy: %w", err)
}

op, err = client.UpdateIAMRoleAssumePolicy(ctx, role.ID, *assumeRolePolicy)
if err != nil {
return err
}
utils.DecorateAsyncOperation("Updating IAM assume role policy...", func() {
_, err = client.Wait(ctx, op, v3.OperationStateSuccess)
})
if err != nil {
return err
}
}

if !globalstate.Quiet {
Expand Down
Loading