Skip to content
Merged
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
63 changes: 63 additions & 0 deletions cmd/iam/accesselevations/accesselevations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package accesselevations

import (
"fmt"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/formattime"
"github.com/thalassa-cloud/cli/internal/shared"
clientiam "github.com/thalassa-cloud/client-go/iam"
)

// AccessElevationsCmd manages temporary IAM access elevation requests.
var AccessElevationsCmd = &cobra.Command{
Use: "access-elevations",
Aliases: []string{"access-elevation", "elevations", "ae"},
Short: "Request and review temporary IAM access elevations",
Long: `Request temporary elevation to an IAM policy, and approve, reject, or revoke
requests in the current organisation/project scope.

Use --mine on list/get to work with your own requests. create and cancel always
act on your requests; approve, reject, and revoke use the approver API.`,
}

var (
noHeader bool
showExactTime bool
mineFlag bool
)

func printAccessElevationDetails(req *clientiam.IamAccessElevationRequest, exactTime bool) {
fmt.Printf("Identity: %s\n", req.Identity)
fmt.Printf("Status: %s\n", req.Status)
fmt.Printf("Reason: %s\n", req.Reason)
fmt.Printf("Expires: %s\n", formattime.FormatTime(req.RequestedExpiresAt.Local(), exactTime))
fmt.Printf("Created: %s\n", formattime.FormatTime(req.CreatedAt.Local(), exactTime))
if req.UpdatedAt != nil {
fmt.Printf("Updated: %s\n", formattime.FormatTime(req.UpdatedAt.Local(), exactTime))
}
if req.Project != nil {
fmt.Printf("Project: %s (%s)\n", req.Project.Name, req.Project.Identity)
} else if req.ProjectId != "" {
fmt.Printf("Project: %s\n", req.ProjectId)
}
if req.IamPolicy != nil {
fmt.Printf("Policy: %s (%s)\n", req.IamPolicy.Name, req.IamPolicy.Identity)
}
if req.Requester != nil {
fmt.Printf("Requester: %s\n", shared.UserPtrDisplay(req.Requester))
}
if req.ReviewedAt != nil {
fmt.Printf("Reviewed: %s\n", formattime.FormatTime(req.ReviewedAt.Local(), exactTime))
}
if req.ReviewedBy != nil {
fmt.Printf("Reviewed by: %s\n", shared.UserPtrDisplay(req.ReviewedBy))
}
if req.ReviewNote != nil && *req.ReviewNote != "" {
fmt.Printf("Review note: %s\n", *req.ReviewNote)
}
if req.IamPolicyBinding != nil {
fmt.Printf("Binding: %s (%s)\n", req.IamPolicyBinding.Name, req.IamPolicyBinding.Identity)
}
}
48 changes: 48 additions & 0 deletions cmd/iam/accesselevations/cancel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package accesselevations

import (
"fmt"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
)

var cancelForce bool

var cancelCmd = &cobra.Command{
Use: "cancel <identity>",
Short: "Cancel your pending access elevation request",
Args: cobra.ExactArgs(1),
ValidArgsFunction: completion.CompleteIAMMyAccessElevationIdentity,
RunE: func(cmd *cobra.Command, args []string) error {
ok, err := shared.PromptDestructiveUnlessForce(cancelForce, fmt.Sprintf(
"Are you sure you want to cancel this access elevation request?\n Request: %s\n", args[0]))
if err != nil {
return err
}
if !ok {
return nil
}

client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

req, err := client.IAM().CancelAccessElevation(cmd.Context(), args[0])
if err != nil {
return fmt.Errorf("failed to cancel access elevation: %w", err)
}

printAccessElevationDetails(req, false)
return nil
},
}

func init() {
AccessElevationsCmd.AddCommand(cancelCmd)
cancelCmd.Flags().BoolVar(&cancelForce, shared.ForceKey, false, "Skip the confirmation prompt")
}
94 changes: 94 additions & 0 deletions cmd/iam/accesselevations/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package accesselevations

import (
"fmt"
"time"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/iamresolve"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientiam "github.com/thalassa-cloud/client-go/iam"
)

var (
createPolicy string
createReason string
createDuration string
createExpiresAt string
createLabels []string
createAnnotations []string
)

var createCmd = &cobra.Command{
Use: "create",
Short: "Request temporary elevation to an IAM policy",
Long: `Create an access elevation request for yourself against a policy.

Exactly one of --duration or --expires-at is required.`,
Example: ` tcloud iam access-elevations create --policy admin --reason "incident response" --duration 2h
tcloud iam access-elevations create --policy pol-xxx --reason "break-glass" --expires-at 2026-10-01T20:00:00Z`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if createExpiresAt != "" && createDuration != "" {
return fmt.Errorf("specify at most one of --expires-at or --duration")
}
if createExpiresAt == "" && createDuration == "" {
return fmt.Errorf("specify one of --duration or --expires-at")
}

client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

policy, err := iamresolve.ResolveIamPolicyRef(cmd.Context(), client.IAM(), createPolicy)
if err != nil {
return err
}

create := clientiam.CreateAccessElevationRequest{
PolicyIdentity: policy.Identity,
Reason: createReason,
Duration: createDuration,
Labels: shared.KeyValuePairsToMap(createLabels),
Annotations: shared.KeyValuePairsToMap(createAnnotations),
}
if create.Labels == nil {
create.Labels = map[string]string{}
}
if create.Annotations == nil {
create.Annotations = map[string]string{}
}
if createExpiresAt != "" {
t, err := time.Parse(time.RFC3339, createExpiresAt)
if err != nil {
return fmt.Errorf("--expires-at must be RFC3339 (e.g. 2026-12-31T23:59:59Z): %w", err)
}
create.ExpiresAt = &t
}

req, err := client.IAM().CreateAccessElevation(cmd.Context(), create)
if err != nil {
return fmt.Errorf("failed to create access elevation: %w", err)
}

printAccessElevationDetails(req, false)
return nil
},
}

func init() {
AccessElevationsCmd.AddCommand(createCmd)
createCmd.Flags().StringVar(&createPolicy, "policy", "", "IAM policy identity, slug, or name (required)")
createCmd.Flags().StringVar(&createReason, "reason", "", "Reason for the elevation request (required)")
createCmd.Flags().StringVar(&createDuration, "duration", "", "Relative TTL (e.g. 2h, 24h)")
createCmd.Flags().StringVar(&createExpiresAt, "expires-at", "", "Absolute expiry time (RFC3339)")
createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)")
createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)")
_ = createCmd.MarkFlagRequired("policy")
_ = createCmd.MarkFlagRequired("reason")
_ = createCmd.RegisterFlagCompletionFunc("policy", completion.CompleteIAMPolicyIdentityFlag)
}
44 changes: 44 additions & 0 deletions cmd/iam/accesselevations/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package accesselevations

import (
"fmt"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientiam "github.com/thalassa-cloud/client-go/iam"
)

var getCmd = &cobra.Command{
Use: "get <identity>",
Aliases: []string{"view", "show", "describe"},
Short: "Show an access elevation request",
Args: cobra.ExactArgs(1),
ValidArgsFunction: completion.CompleteIAMAccessElevationIdentity,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

var req *clientiam.IamAccessElevationRequest
if mineFlag {
req, err = client.IAM().GetMyAccessElevation(cmd.Context(), args[0])
} else {
req, err = client.IAM().GetAccessElevation(cmd.Context(), args[0])
}
if err != nil {
return fmt.Errorf("failed to get access elevation: %w", err)
}

printAccessElevationDetails(req, showExactTime)
return nil
},
}

func init() {
AccessElevationsCmd.AddCommand(getCmd)
getCmd.Flags().BoolVar(&mineFlag, "mine", false, "Fetch via your own requests API")
getCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time")
}
69 changes: 69 additions & 0 deletions cmd/iam/accesselevations/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package accesselevations

import (
"fmt"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/table"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientiam "github.com/thalassa-cloud/client-go/iam"
)

var listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List access elevation requests",
Long: `List access elevation requests visible to approvers in the current scope.

Use --mine to list only requests you created.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

var requests []clientiam.IamAccessElevationRequest
if mineFlag {
requests, err = client.IAM().ListMyAccessElevations(cmd.Context())
} else {
requests, err = client.IAM().ListAccessElevations(cmd.Context())
}
if err != nil {
return fmt.Errorf("failed to list access elevations: %w", err)
}

body := make([][]string, 0, len(requests))
for _, req := range requests {
policy := ""
if req.IamPolicy != nil {
policy = req.IamPolicy.Name
if policy == "" {
policy = req.IamPolicy.Identity
}
}
requester := shared.UserPtrDisplay(req.Requester)
body = append(body, []string{
req.Identity,
string(req.Status),
policy,
requester,
req.Reason,
})
}
if noHeader {
table.Print(nil, body)
} else {
table.Print([]string{"ID", "Status", "Policy", "Requester", "Reason"}, body)
}
return nil
},
}

func init() {
AccessElevationsCmd.AddCommand(listCmd)
listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers")
listCmd.Flags().BoolVar(&mineFlag, "mine", false, "List only requests you created")
}
Loading
Loading