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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- GCP support for `grant`, `grant list`, `grant status` and `grant revoke`: `--provider gcp`, GCP included in the multi-provider fan-out, and the `PROJECT`/`FOLDER`/`GCP_ORGANIZATION` workspace types rendered in the interactive selector. `grant env` stays AWS-only (the SCA API spec defines no GCP credential shape) and `grant request submit` explicitly rejects GCP. Not yet verified against a live GCP tenant

### Fixed

- `grant env` no longer performs an elevation before validating the provider. Previously `grant env --provider azure` created a real SCA session and recorded a session timestamp before failing with "no credentials returned", leaving an unwanted active session behind. The provider is now validated before the elevation request is issued

### Changed

- Rebranded user-facing references from CyberArk to Idira (formerly CyberArk) in the README and CLI help/prompt text, following the Palo Alto Networks rebrand. No functional or API changes; SDK import paths (`github.com/cyberark/idsec-sdk-golang`), `*.cyberark.cloud` URLs, and environment variables are unchanged.
Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Custom `SCAAccessService` follows SDK conventions:
## SCA Access API
- **Base URL:** `https://{subdomain}.sca.{platform_domain}/api`
- **Endpoints:**
- `GET /api/access/{CSP}/eligibility` — list eligible targets
- `GET /api/access/{CSP}/eligibility` — list eligible targets (`AZURE`, `AWS`, `GCP`)
- `POST /api/access/elevate` — request JIT elevation (AWS responses include `accessCredentials` JSON string)
- `GET /api/access/sessions` — list active sessions
- `POST /api/access/sessions/revoke` — revoke sessions by ID (request: `sessionIds[]`, response: `SessionRevocationInfo[]`)
Expand Down Expand Up @@ -84,7 +84,7 @@ Custom `SCAAccessService` follows SDK conventions:
## CLI
- `spf13/cobra` for CLI framework
- `Iilun/survey/v2` for interactive prompts
- `grant env` — performs elevation, outputs only `export` statements (no human text); usage: `eval $(grant env --provider aws)`; supports `--refresh`
- `grant env` — **AWS only**; performs elevation, outputs only `export` statements (no human text); usage: `eval $(grant env --provider aws)`; supports `--refresh`. Non-AWS targets are rejected by `requireAWSTarget` (passed as the `preElevate` hook to `resolveAndElevate`) *before* any elevation is issued, so no session is created. It fails closed — an unresolved CSP is rejected too; the `AccessCredentials == nil` check remains as a fallback
- `grant list` — list eligible targets and groups without triggering elevation; supports `--provider`, `--groups`, `--refresh`, `--output json`; used by LLMs to discover available targets programmatically
- `grant revoke` — revoke sessions: direct (`grant revoke <id>`), `--all`, or interactive multi-select; `--yes` skips confirmation
- `grant request` — manage access requests through approval workflow; subcommands: `submit`, `list`, `get`, `cancel`, `approve`, `reject`
Expand All @@ -108,7 +108,8 @@ Custom `SCAAccessService` follows SDK conventions:
- `--groups` flag on root command shows only Entra ID groups in the interactive selector
- `--group` / `-g` flag on root command for direct group membership elevation (`grant --group "Cloud Admins"`)
- Root command unified selector shows both cloud roles and Entra ID groups; groups use `/eligibility/groups` and `/elevate/groups` API endpoints
- Multi-CSP: omitting `--provider` fetches eligibility from all supported CSPs and merges results
- Multi-CSP: omitting `--provider` fetches eligibility from all supported CSPs (`supportedCSPs` in `cmd/root.go` — Azure, AWS, GCP) and merges results; a CSP that errors is skipped
- GCP: `list`/elevate/`status`/`revoke` only. Workspace types `PROJECT`/`FOLDER`/`GCP_ORGANIZATION`; no `accessCredentials` in the API spec, so `grant env` stays AWS-only and `grant request submit` rejects GCP (`rejectGCPWorkspace`, applied after target resolution so `--role-id` cannot skip it). **Untested against a live GCP tenant**
- `--refresh` bypasses eligibility cache on `grant` and `grant env`
- `fetchEligibility()` and `resolveTargetCSP()` in `cmd/root.go` — shared by root, env, and favorites

Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# grant

A CLI tool for elevating cloud permissions (Azure, AWS) via Idira (formerly CyberArk) Secure Cloud Access (SCA) — without leaving the terminal.
A CLI tool for elevating cloud permissions (Azure, AWS, GCP) via Idira (formerly CyberArk) Secure Cloud Access (SCA) — without leaving the terminal.

![grant demo](demo/demo.gif)

## Overview

`grant` enables terminal-based cloud permission elevation (Azure, AWS) through Idira SCA. It wraps the `idsec-sdk-golang` SDK for authentication and builds a custom SCA Access API client for JIT role elevation.
`grant` enables terminal-based cloud permission elevation (Azure, AWS, GCP) through Idira SCA. It wraps the `idsec-sdk-golang` SDK for authentication and builds a custom SCA Access API client for JIT role elevation.

- **Azure:** SCA creates a JIT RBAC role assignment — your existing `az` CLI session picks up the elevated permissions automatically.
- **AWS:** SCA returns temporary credentials. Use `grant env` to export them: `eval $(grant env --provider aws)`
- **AWS:** SCA returns temporary credentials. Use `grant env` to export them: `eval $(grant env --provider aws)` — `grant env` is AWS-only, since Azure and GCP elevations return no credentials.
- **GCP:** SCA grants the role on the project, folder or organization — your existing `gcloud` CLI session picks it up. **Untested against a live GCP tenant.**

## Usage

Expand All @@ -23,6 +24,7 @@ grant
# Elevate for a specific provider
grant --provider azure
grant --provider aws
grant --provider gcp

# Direct elevation with target and role
grant --provider azure --target "Prod-EastUS" --role "Contributor"
Expand Down Expand Up @@ -110,7 +112,7 @@ Running `grant` with no subcommand elevates cloud permissions (the core behavior
|---------|-------------|
| `grant` | Elevate cloud permissions (interactive, direct with `--target`/`--role`, or `--favorite`) |
| `configure` | Configure Identity URL and username (optional — `login` auto-configures) |
| `env` | Elevate and output AWS credential export statements for `eval $(grant env)` |
| `env` | Elevate and output AWS credential export statements for `eval $(grant env)` (AWS only) |
| `list` | List eligible targets and groups without elevation (`--provider`, `--groups`, `--output json`) |
| `login` | Authenticate to Idira Identity (MFA handled interactively) |
| `logout` | Clear cached tokens from keyring |
Expand Down Expand Up @@ -180,7 +182,7 @@ favorites:
| Azure CLI doesn't see new role after elevation | Refresh token: `az account get-access-token --output none` (or `az account clear && az login`) |
| "No eligible targets found" | Verify SCA policies with your Idira admin; try without `--provider` to see all targets |
| "Failed to elevate" | Check `grant status` for active sessions; verify target/role names |
| `grant env` errors for Azure | `env` is AWS-only — Azure doesn't return credentials, use `grant` directly |
| `grant env` errors for Azure/GCP | `env` is AWS-only — Azure and GCP return no credentials, use `grant` directly |
| Permission denied accessing keyring (Linux) | Install and start `gnome-keyring` or `kwalletmanager` |

## Development
Expand Down
44 changes: 40 additions & 4 deletions cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"errors"
"fmt"
"strings"

"github.com/aaearon/grant-cli/internal/config"
"github.com/aaearon/grant-cli/internal/sca/models"
Expand All @@ -14,12 +15,15 @@ import (
func newEnvCommand(runFn func(*cobra.Command, []string) error) *cobra.Command {
cmd := &cobra.Command{
Use: "env",
Short: "Output AWS credential export statements",
Long: `Perform elevation and output AWS credential export statements.
Short: "Output AWS credential export statements (AWS only)",
Long: `Perform an AWS elevation and output credential export statements.

Runs the full elevation flow, then prints only shell export statements
suitable for eval. No human-readable messages are printed to stdout.

Only AWS is supported: Azure and GCP elevations return no credentials —
they apply to your existing az/gcloud CLI session, so use 'grant' instead.

Usage:
eval $(grant env --provider aws --target "Account" --role "AdminAccess")
eval $(grant env --favorite my-aws-fav)
Expand All @@ -29,7 +33,7 @@ Usage:
RunE: runFn,
}

cmd.Flags().StringP("provider", "p", "", "Cloud provider: azure, aws (omit to show all)")
cmd.Flags().StringP("provider", "p", "", "Cloud provider (aws only)")
cmd.Flags().StringP("target", "t", "", "Target name (account, subscription, etc.)")
cmd.Flags().StringP("role", "r", "", "Role name")
cmd.Flags().StringP("favorite", "f", "", "Use a saved favorite (see 'grant favorites list')")
Expand Down Expand Up @@ -77,6 +81,37 @@ func NewEnvCommandWithDeps(
})
}

// requireAWSTarget rejects any target that is not known to be AWS before an
// elevation is performed. It fails closed: an unresolved CSP is rejected rather
// than elevated speculatively, since the whole point of the pre-flight check is
// to avoid creating a session we cannot use.
func requireAWSTarget(target *models.EligibleTarget) error {
switch target.CSP {
case models.CSPAWS:
return nil
case "":
return fmt.Errorf(
"could not determine the cloud provider for target %q; grant env is only supported for AWS — run 'grant --provider aws' or pass --provider",
target.WorkspaceName)
}
provider := strings.ToLower(string(target.CSP))
return fmt.Errorf(
"grant env is only supported for AWS; %s elevations return no credentials — run 'grant --provider %s' instead and use your existing %s CLI session",
provider, provider, cliForCSP(target.CSP))
}

// cliForCSP names the native CLI whose session a non-AWS elevation applies to.
func cliForCSP(csp models.CSP) string {
switch csp {
case models.CSPAzure:
return "az"
case models.CSPGCP:
return "gcloud"
default:
return "cloud provider"
}
}

func runEnvWithDeps(
cmd *cobra.Command,
flags *elevateFlags,
Expand All @@ -87,14 +122,15 @@ func runEnvWithDeps(
selector targetSelector,
cfg *config.Config,
) error {
res, err := resolveAndElevate(flags, profile, authLoader, eligibilityLister, elevateService, selector, cfg)
res, err := resolveAndElevate(flags, profile, authLoader, eligibilityLister, elevateService, selector, cfg, requireAWSTarget)
if err != nil {
return err
}

// Record session timestamp for remaining-time tracking (best-effort)
recordSessionTimestamp(res.result.SessionID)

// Defense in depth: AWS itself returning no credentials.
if res.result.AccessCredentials == nil {
return errors.New("no credentials returned; grant env is only supported for AWS elevations")
}
Expand Down
Loading
Loading