Skip to content

Commit

Permalink
feat!: use proper terms for 'azure resources' type (#59)
Browse files Browse the repository at this point in the history
* feat!: use proper terms for "azure resource" type

* docs: update to reflect new term for "azure resource" type
  • Loading branch information
netr0m authored Oct 8, 2024
1 parent 04a6e49 commit 6411902
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 153 deletions.
87 changes: 44 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/netr0m/az-pim-cli.svg)](https://pkg.go.dev/github.com/netr0m/az-pim-cli)

`az-pim-cli` eases the process of listing and activating Azure PIM roles by allowing activation via the command line. Authentication is handled with the `azure.identity` library by utilizing the `AzureCLICredential` method.
It currently supports ['azure resources'](#azure-resources) and ['groups'](#groups).

## Install
### Install with `go install`
Expand Down Expand Up @@ -35,13 +36,13 @@ This tool depends on [`az-cli`](https://learn.microsoft.com/en-us/cli/azure/) fo
```bash
$ az-pim-cli --help
az-pim-cli is a utility that allows the user to list and activate eligible role assignments
from Azure Entra ID Privileged Identity Management (PIM) directly from the command line
from Azure Entra ID Privileged Identity Management (PIM) directly from the command line.

Usage:
az-pim-cli [command]

Available Commands:
activate Sends a request to Azure PIM to activate the given role
activate Send a request to Azure PIM to activate a role assignment
completion Generate the autocompletion script for the specified shell
help Help about any command
list Query Azure PIM for eligible role assignments
Expand All @@ -55,35 +56,35 @@ Use "az-pim-cli [command] --help" for more information about a command.

```

### List eligible role assignments (Azure resources)
### List eligible role assignments

#### Azure resources
> List [azure resources](https://portal.azure.com/#view/Microsoft_Azure_PIMCommon/ActivationMenuBlade/~/azurerbac)
```bash
$ az-pim-cli list --help
Query Azure PIM for eligible role assignments
$ az-pim-cli list resources --help
Query Azure PIM for eligible resource assignments (azure resources)

Usage:
az-pim-cli list [flags]
az-pim-cli list [command]
az-pim-cli list resource [flags]

Aliases:
list, l, ls

Available Commands:
group Query Azure PIM for eligible group assignments
resource, r, res, resource, resources, sub, subs, subscriptions

Flags:
-h, --help help for list
-h, --help help for resource

Global Flags:
-c, --config string config file (default is $HOME/.az-pim-cli.yaml)

Use "az-pim-cli list [command] --help" for more information about a command.

```

### List eligible group assignments (Entra Groups)
#### Groups
> List [groups](https://portal.azure.com/#view/Microsoft_Azure_PIMCommon/ActivationMenuBlade/~/aadgroup)
> :warning: Requires an access token with the appropriate scope. See [Token for Entra ID Groups](#token-for-entra-id-groups) for more details.
```bash
$ az-pim-cli list group --help
$ az-pim-cli list groups --help
Query Azure PIM for eligible group assignments

Usage:
Expand All @@ -101,41 +102,41 @@ Global Flags:

```

### Activate a role (Azure resources)
### Activate a role

#### Azure resources
> Activate [azure resources](https://portal.azure.com/#view/Microsoft_Azure_PIMCommon/ActivationMenuBlade/~/azurerbac)
```bash
$ az-pim-cli activate --help
Sends a request to Azure PIM to activate the given role
$ az-pim-cli activate resource --help
Sends a request to Azure PIM to activate the given resource (azure resources)

Usage:
az-pim-cli activate [flags]
az-pim-cli activate [command]
az-pim-cli activate resource [flags]

Aliases:
activate, a, ac, act

Available Commands:
group Sends a request to Azure PIM to activate the given group
resource, r, res, resource, resources, sub, subs, subscriptions

Flags:
-h, --help help for resource

Global Flags:
-c, --config string config file (default is $HOME/.az-pim-cli.yaml)
--dry-run Display the resource that would be activated, without requesting the activation
-d, --duration int Duration in minutes that the role should be activated for (default 480)
-h, --help help for activate
-n, --name string The name of the resource to activate
-p, --prefix string The name prefix of the resource to activate (e.g. 'S399'). Alternative to 'name'.
--reason string Reason for the activation (default "config")
-r, --role string Specify the role to activate, if multiple roles are found for a resource (e.g. 'Owner' and 'Contributor')
-T, --ticket-number string Ticket number for the activation
--ticket-system string Ticket system for the activation

Global Flags:
-c, --config string config file (default is $HOME/.az-pim-cli.yaml)

Use "az-pim-cli activate [command] --help" for more information about a command.

```
### Activate a role (Entra Groups)
#### Groups
> Activate [groups](https://portal.azure.com/#view/Microsoft_Azure_PIMCommon/ActivationMenuBlade/~/aadgroup)
> :warning: Requires an access token with the appropriate scope. See [Token for Entra ID Groups](#token-for-entra-id-groups) for more details.
```bash
$ az-pim-cli activate group --help
Sends a request to Azure PIM to activate the given group
Expand All @@ -148,7 +149,7 @@ Aliases:
Flags:
-h, --help help for group
-t, --token string An access token for the PIM Groups API (required). Consult the README for more information.
-t, --token string An access token for the PIM 'Entra Roles' and 'Groups' API (required). Consult the README for more information.
Global Flags:
-c, --config string config file (default is $HOME/.az-pim-cli.yaml)
Expand All @@ -167,30 +168,30 @@ Global Flags:
#### Azure resources
```bash
# List eligible Azure resource role assignments
$ az-pim-cli list
$ az-pim-cli list resources
== S100-Example-Subscription ==
- Contributor
- Owner
== S1337-Another-Subscription ==
- Contributor
# Activate the first matching role in a subscription with the prefix 'S100'
$ az-pim-cli activate --prefix S100
2024/05/31 15:05:25 Activating role 'Contributor' in subscription 'S100-Example-Subscription' with reason 'config' (ticket: [])
# Activate the first matching role for a resource with the prefix 'S100'
$ az-pim-cli activate resource --prefix S100
2024/05/31 15:05:25 Activating role 'Contributor' for resource 'S100-Example-Subscription' with reason 'config' (ticket: [])
2024/05/31 15:05:34 The role 'Contributor' in 'S100-Example-Subscription' is now Provisioned
# Activate a specific role ('Owner') in a subscription with the prefix 's100'
$ az-pim-cli activate --prefix s100 --role owner
2024/05/31 15:06:25 Activating role 'Owner' in subscription 'S100-Example-Subscription' with reason 'config' (ticket: [])
# Activate a specific role ('Owner') for a resource with the prefix 's100'
$ az-pim-cli activate resource --prefix s100 --role owner
2024/05/31 15:06:25 Activating role 'Owner' for resource 'S100-Example-Subscription' with reason 'config' (ticket: [])
2024/05/31 15:06:34 The role 'Owner' in 'S100-Example-Subscription' is now Provisioned
# Activate a role and specify a ticket number for the activation
$ az-pim-cli activate --name S100-Example-Subscription --role Owner --ticket-system Jira --ticket-number T-1337
2024/05/31 15:06:25 Activating role 'Owner' in subscription 'S100-Example-Subscription' with reason 'config' (ticket: T-1337 [Jira])
$ az-pim-cli activate resource --name S100-Example-Subscription --role Owner --ticket-system Jira --ticket-number T-1337
2024/05/31 15:06:25 Activating role 'Owner' for resource 'S100-Example-Subscription' with reason 'config' (ticket: T-1337 [Jira])
2024/05/31 15:06:34 The role 'Owner' in 'S100-Example-Subscription' is now Provisioned
```
#### Entra groups
#### Groups
```bash
# List eligible group assignments
$ az-pim-cli list groups
Expand Down
26 changes: 17 additions & 9 deletions cmd/activate.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,25 @@ var dryRun bool
var activateCmd = &cobra.Command{
Use: "activate",
Aliases: []string{"a", "ac", "act"},
Short: "Sends a request to Azure PIM to activate the given role",
Short: "Send a request to Azure PIM to activate a role assignment",
Run: func(cmd *cobra.Command, args []string) {},
}

var activateResourceCmd = &cobra.Command{
Use: "resource",
Aliases: []string{"r", "res", "resource", "resources", "sub", "subs", "subscriptions"},
Short: "Sends a request to Azure PIM to activate the given resource (azure resources)",
Run: func(cmd *cobra.Command, args []string) {
token := pim.GetPIMAccessTokenAzureCLI(pim.AZ_PIM_SCOPE)
subjectId := pim.GetUserInfo(token).ObjectId

eligibleRoleAssignments := pim.GetEligibleRoleAssignments(token)
roleAssignment := utils.GetRoleAssignment(name, prefix, roleName, eligibleRoleAssignments)
eligibleResourceAssignments := pim.GetEligibleResourceAssignments(token)
resourceAssignment := utils.GetResourceAssignment(name, prefix, roleName, eligibleResourceAssignments)

log.Printf(
"Activating role '%s' in subscription '%s' with reason '%s' (ticket: %s [%s])",
roleAssignment.Properties.ExpandedProperties.RoleDefinition.DisplayName,
roleAssignment.Properties.ExpandedProperties.Scope.DisplayName,
"Activating role '%s' for resource '%s' with reason '%s' (ticket: %s [%s])",
resourceAssignment.Properties.ExpandedProperties.RoleDefinition.DisplayName,
resourceAssignment.Properties.ExpandedProperties.Scope.DisplayName,
reason,
ticketNumber,
ticketSystem,
Expand All @@ -45,8 +52,8 @@ var activateCmd = &cobra.Command{
log.Printf("Skipping activation due to 'dry-run'.")
os.Exit(0)
}
requestResponse := pim.RequestRoleAssignment(subjectId, roleAssignment, duration, reason, ticketSystem, ticketNumber, token)
log.Printf("The role '%s' in '%s' is now %s", roleAssignment.Properties.ExpandedProperties.RoleDefinition.DisplayName, roleAssignment.Properties.ExpandedProperties.Scope.DisplayName, requestResponse.Properties.Status)
requestResponse := pim.RequestResourceAssignment(subjectId, resourceAssignment, duration, reason, ticketSystem, ticketNumber, token)
log.Printf("The role '%s' in '%s' is now %s", resourceAssignment.Properties.ExpandedProperties.RoleDefinition.DisplayName, resourceAssignment.Properties.ExpandedProperties.Scope.DisplayName, requestResponse.Properties.Status)
},
}

Expand Down Expand Up @@ -80,6 +87,7 @@ var activateGroupCmd = &cobra.Command{

func init() {
rootCmd.AddCommand(activateCmd)
activateCmd.AddCommand(activateResourceCmd)
activateCmd.AddCommand(activateGroupCmd)

// Flags
Expand All @@ -92,7 +100,7 @@ func init() {
activateCmd.PersistentFlags().StringVarP(&ticketNumber, "ticket-number", "T", "", "Ticket number for the activation")
activateCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Display the resource that would be activated, without requesting the activation")

activateGroupCmd.PersistentFlags().StringVarP(&pimGroupsToken, "token", "t", "", "An access token for the PIM Groups API (required). Consult the README for more information.")
activateGroupCmd.PersistentFlags().StringVarP(&pimGroupsToken, "token", "t", "", "An access token for the PIM 'Entra Roles' and 'Groups' API (required). Consult the README for more information.")
activateGroupCmd.MarkPersistentFlagRequired("token") //nolint:errcheck

activateCmd.MarkFlagsOneRequired("name", "prefix")
Expand Down
12 changes: 10 additions & 2 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ var listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"l", "ls"},
Short: "Query Azure PIM for eligible role assignments",
Run: func(cmd *cobra.Command, args []string) {},
}

var listResourceCmd = &cobra.Command{
Use: "resource",
Aliases: []string{"r", "res", "resource", "resources", "sub", "subs", "subscriptions"},
Short: "Query Azure PIM for eligible resource assignments (azure resources)",
Run: func(cmd *cobra.Command, args []string) {
token := pim.GetPIMAccessTokenAzureCLI(pim.AZ_PIM_SCOPE)

eligibleRoleAssignments := pim.GetEligibleRoleAssignments(token)
utils.PrintEligibleRoles(eligibleRoleAssignments)
eligibleResourceAssignments := pim.GetEligibleResourceAssignments(token)
utils.PrintEligibleResources(eligibleResourceAssignments)
},
}

Expand All @@ -34,6 +41,7 @@ var listGroupCmd = &cobra.Command{

func init() {
rootCmd.AddCommand(listCmd)
listCmd.AddCommand(listResourceCmd)
listCmd.AddCommand(listGroupCmd)

listGroupCmd.PersistentFlags().StringVarP(&pimGroupsToken, "token", "t", "", "An access token for the PIM Groups API (required). Consult the README for more information.")
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var rootCmd = &cobra.Command{
Use: "az-pim-cli",
Short: "A utility to list and activate Azure AD PIM roles from the CLI",
Long: `az-pim-cli is a utility that allows the user to list and activate eligible role assignments
from Azure Entra ID Privileged Identity Management (PIM) directly from the command line`,
from Azure Entra ID Privileged Identity Management (PIM) directly from the command line.`,
}

// Execute adds all child commands to the root command and sets flags appropriately.
Expand Down
44 changes: 22 additions & 22 deletions pkg/pim/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ func Request(request *PIMRequest, responseModel any) any {
return responseModel
}

func GetEligibleRoleAssignments(token string) *RoleAssignmentResponse {
func GetEligibleResourceAssignments(token string) *ResourceAssignmentResponse {
var params = map[string]string{
"api-version": AZ_PIM_API_VERSION,
"$filter": "asTarget()",
}
responseModel := &RoleAssignmentResponse{}
responseModel := &ResourceAssignmentResponse{}
_ = Request(&PIMRequest{
Url: fmt.Sprintf("%s/%s/roleEligibilityScheduleInstances", AZ_PIM_BASE_URL, AZ_PIM_BASE_PATH),
Token: token,
Expand All @@ -137,18 +137,18 @@ func GetEligibleGroupAssignments(token string, subjectId string) *GroupAssignmen
return responseModel
}

func ValidateRoleAssignmentRequest(scope string, roleAssignmentRequest RoleAssignmentRequestRequest, token string) bool {
func ValidateResourceAssignmentRequest(scope string, resourceAssignmentRequest ResourceAssignmentRequestRequest, token string) bool {
var params = map[string]string{
"api-version": AZ_PIM_API_VERSION,
}

roleAssignmentValidationRequest := roleAssignmentRequest
roleAssignmentValidationRequest.Properties.Justification = "validation only call"
roleAssignmentValidationRequest.Properties.TicketInfo.TicketNumber = "Evaluate Only"
roleAssignmentValidationRequest.Properties.TicketInfo.TicketSystem = "Evaluate Only"
roleAssignmentValidationRequest.Properties.IsValidationOnly = true
resourceAssignmentValidationRequest := resourceAssignmentRequest
resourceAssignmentValidationRequest.Properties.Justification = "validation only call"
resourceAssignmentValidationRequest.Properties.TicketInfo.TicketNumber = "Evaluate Only"
resourceAssignmentValidationRequest.Properties.TicketInfo.TicketSystem = "Evaluate Only"
resourceAssignmentValidationRequest.Properties.IsValidationOnly = true

validationResponse := &RoleAssignmentRequestResponse{}
validationResponse := &ResourceAssignmentRequestResponse{}
_ = Request(&PIMRequest{
Url: fmt.Sprintf(
"%s/%s/%s/roleAssignmentScheduleRequests/%s/validate",
Expand All @@ -160,18 +160,18 @@ func ValidateRoleAssignmentRequest(scope string, roleAssignmentRequest RoleAssig
Token: token,
Method: "POST",
Params: params,
Payload: roleAssignmentValidationRequest,
Payload: resourceAssignmentValidationRequest,
}, validationResponse)

if IsRoleAssignmentRequestFailed(validationResponse) {
if IsResourceAssignmentRequestFailed(validationResponse) {
log.Printf("ERROR: The role assignment validation failed with status '%s'", validationResponse.Properties.Status)
log.Fatalln(validationResponse)
return false
}
if IsRoleAssignmentRequestOK(validationResponse) {
if IsResourceAssignmentRequestOK(validationResponse) {
return true
}
if IsRoleAssignmentRequestPending(validationResponse) {
if IsResourceAssignmentRequestPending(validationResponse) {
log.Printf("WARNING: The role assignment request is pending with status '%s'", validationResponse.Properties.Status)
return true
}
Expand Down Expand Up @@ -214,17 +214,17 @@ func ValidateGroupAssignmentRequest(groupAssignmentRequest GroupAssignmentReques
return false
}

func RequestRoleAssignment(subjectId string, roleAssignment *RoleAssignment, duration int, reason string, ticketSystem string, ticketNumber string, token string) *RoleAssignmentRequestResponse {
func RequestResourceAssignment(subjectId string, resourceAssignment *ResourceAssignment, duration int, reason string, ticketSystem string, ticketNumber string, token string) *ResourceAssignmentRequestResponse {
var params = map[string]string{
"api-version": AZ_PIM_API_VERSION,
}

roleAssignmentRequest := &RoleAssignmentRequestRequest{
Properties: RoleAssignmentRequestProperties{
resourceAssignmentRequest := &ResourceAssignmentRequestRequest{
Properties: ResourceAssignmentRequestProperties{
PrincipalId: subjectId,
RoleDefinitionId: roleAssignment.Properties.ExpandedProperties.RoleDefinition.Id,
RoleDefinitionId: resourceAssignment.Properties.ExpandedProperties.RoleDefinition.Id,
RequestType: "SelfActivate",
LinkedRoleEligibilityScheduleId: roleAssignment.Properties.RoleEligibilityScheduleId,
LinkedRoleEligibilityScheduleId: resourceAssignment.Properties.RoleEligibilityScheduleId,
Justification: reason,
ScheduleInfo: &ScheduleInfo{
StartDateTime: nil,
Expand All @@ -238,11 +238,11 @@ func RequestRoleAssignment(subjectId string, roleAssignment *RoleAssignment, dur
IsActivativation: true,
},
}
scope := roleAssignment.Properties.ExpandedProperties.Scope.Id[1:]
scope := resourceAssignment.Properties.ExpandedProperties.Scope.Id[1:]

ValidateRoleAssignmentRequest(scope, *roleAssignmentRequest, token)
ValidateResourceAssignmentRequest(scope, *resourceAssignmentRequest, token)

responseModel := &RoleAssignmentRequestResponse{}
responseModel := &ResourceAssignmentRequestResponse{}
_ = Request(&PIMRequest{
Url: fmt.Sprintf(
"%s/%s/%s/roleAssignmentScheduleRequests/%s",
Expand All @@ -254,7 +254,7 @@ func RequestRoleAssignment(subjectId string, roleAssignment *RoleAssignment, dur
Token: token,
Method: "PUT",
Params: params,
Payload: roleAssignmentRequest,
Payload: resourceAssignmentRequest,
}, responseModel)

return responseModel
Expand Down
Loading

0 comments on commit 6411902

Please sign in to comment.