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
71 changes: 71 additions & 0 deletions cmd/iam/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,74 @@ func iamPolicyFromJSON(data []byte) (*v3.IAMPolicy, error) {

return &policy, nil
}

type iamAssumeRolePolicyOutput struct {
Rules []iamPolicyServiceRuleOutput `json:"rules"`
}

func (o *iamAssumeRolePolicyOutput) ToJSON() { output.JSON(o) }
func (o *iamAssumeRolePolicyOutput) ToText() { output.Text(o) }
func (o *iamAssumeRolePolicyOutput) ToTable() {
t := table.NewTable(os.Stdout)

t.SetHeader([]string{
"Rule Action",
"Rule Expression",
})

// use underlying tablewriter.Render to display table even with empty rows.
defer t.Table.Render()

for _, rule := range o.Rules {
t.Append([]string{
rule.Action,
rule.Expression,
})
}
}

func iamAssumeRolePolicyToOutput(policy *v3.IAMAssumeRolePolicy) *iamAssumeRolePolicyOutput {
if policy == nil {
return nil
}

out := &iamAssumeRolePolicyOutput{
Rules: []iamPolicyServiceRuleOutput{},
}

for _, rule := range policy.Rules {
out.Rules = append(out.Rules, iamPolicyServiceRuleOutput{
Action: string(rule.Action),
Expression: rule.Expression,
})
}

return out
}

func iamAssumeRolePolicyFromJSON(data []byte) (*v3.IAMAssumeRolePolicy, error) {
var obj iamAssumeRolePolicyOutput
err := json.Unmarshal(data, &obj)
if err != nil {
return nil, fmt.Errorf("failed to parse assume role policy: %w", err)
}

policy := v3.IAMAssumeRolePolicy{}

if len(obj.Rules) > 0 {
policy.Rules = []v3.IAMServicePolicyRule{}
for _, rl := range obj.Rules {
rule := v3.IAMServicePolicyRule{
Action: v3.IAMServicePolicyRuleAction(rl.Action),
}

if rl.Expression != "" {
rule.Expression = rl.Expression
}

policy.Rules = append(policy.Rules, rule)
}
}

return &policy, nil
}
93 changes: 93 additions & 0 deletions cmd/iam/iam_role_assume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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"`
ExpiresAT string `json:"expires-at"`
}

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"`

_ 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")
}

if c.Ttl <= 0 {
return errors.New("no valid TTL 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,
ExpiresAT: apiKey.ExpiresAT,
}

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.IAMAssumeRolePolicy

// 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 = iamAssumeRolePolicyFromJSON([]byte(c.AssumeRolePolicy))
if err != nil {
return fmt.Errorf("failed to parse IAM assume role 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
48 changes: 33 additions & 15 deletions cmd/iam/iam_role_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ 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"`
Policy *iamPolicyOutput `json:"policy" output:"-"`
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"`
Policy *iamPolicyOutput `json:"policy" output:"-"`
AssumeRolePolicy *iamAssumeRolePolicyOutput `json:"assume-role-policy" output:"-"`
}

func (o *iamRoleShowOutput) ToJSON() { output.JSON(o) }
Expand All @@ -33,6 +35,10 @@ func (o *iamRoleShowOutput) ToTable() {
fmt.Println("\nPolicy:")
o.Policy.ToTable()
}
if o.AssumeRolePolicy != nil {
fmt.Println("\nAssume Role Policy:")
o.AssumeRolePolicy.ToTable()
}
}

func iamPolicyToOutput(policy *v3.IAMPolicy) *iamPolicyOutput {
Expand Down Expand Up @@ -70,7 +76,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 @@ -114,21 +121,32 @@ func (c *iamRoleShowCmd) CmdRun(_ *cobra.Command, _ []string) error {
}

policy := iamPolicyToOutput(role.Policy)
assumeRolePolicy := iamAssumeRolePolicyToOutput(role.AssumeRolePolicy)

if c.Policy {
if policy == nil {
return errors.New("role policy not found")
}
return c.OutputFunc(policy, nil)
}

if c.AssumeRolePolicy {
if assumeRolePolicy == nil {
return errors.New("role assume role policy not found")
}
return c.OutputFunc(assumeRolePolicy, 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,
Policy: policy,
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,
Policy: policy,
AssumeRolePolicy: assumeRolePolicy,
}

return c.OutputFunc(&out, nil)
Expand Down
Loading
Loading