pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go (210 lines of code) (raw):

// MIT License // // Copyright (c) Microsoft Corporation. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE package sproleassignmentmanager import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strings" "github.com/Azure/mpf/pkg/domain" "github.com/Azure/mpf/pkg/infrastructure/azureAPI" "github.com/google/uuid" log "github.com/sirupsen/logrus" ) type SPRoleAssignmentManager struct { azAPIClient *azureAPI.AzureAPIClients } func NewSPRoleAssignmentManager(subscriptionID string) *SPRoleAssignmentManager { azAPIClient := azureAPI.NewAzureAPIClients(subscriptionID) return &SPRoleAssignmentManager{ azAPIClient: azAPIClient, } } func (r *SPRoleAssignmentManager) CreateUpdateCustomRole(subscription string, role domain.Role, permissions []string) error { retryCount := 3 permissionsToAdd := permissions for i := 0; i < retryCount; i++ { log.Debugf("Creating/Updating Role Definition: %s, Retry: %d", role.RoleDefinitionName, i+1) err := r.createUpdateCustomRole(subscription, role, permissionsToAdd) if err != nil && strings.Contains(err.Error(), "InvalidActionOrNotAction") { errMsg := err.Error() log.Warnf("InvalidActionOrNotAction error occured. Atempting to remove invalid action...") actionsToRemove, err := domain.GetDeleteActionFromInvalidActionOrNotActionError(errMsg) if err != nil { log.Warnf("Could not get actions to remove from error: %s", err.Error()) return err } log.Debug("Filtering Invalid Actions: ", actionsToRemove) permissionsToAdd = filterInvalidActions(permissionsToAdd, actionsToRemove) continue // retry } if err != nil { // not retrying for other errors log.Debugf("Error when updating role: %s", err.Error()) return err } log.Infof("Role definition created/updated successfully") break } return nil } func (r *SPRoleAssignmentManager) createUpdateCustomRole(subscription string, role domain.Role, permissions []string) error { // rgScope := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscription, resourceGroupName) subScope := fmt.Sprintf("/subscriptions/%s", subscription) data := map[string]interface{}{ "assignableScopes": []string{ // rgScope, subScope, }, "description": role.RoleDefinitionName, "id": role.RoleDefinitionResourceID, "name": role.RoleDefinitionID, "permissions": []map[string]interface{}{ { "actions": permissions, "dataActions": []string{}, "notActions": []string{}, "notDataActions": []string{}, }, }, "roleName": role.RoleDefinitionName, "roleType": "CustomRole", // "type": "Microsoft.Authorization/roleDefinitions", } properties := map[string]interface{}{ "properties": data, } // marshal data as json jsonData, err := json.Marshal(properties) if err != nil { return err } //convert to json string jsonString := string(jsonData) // log.Printf("jsonString: %s", jsonString) log.Debugf("jsonString: %s", jsonString) url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s?api-version=2018-01-01-preview", subscription, role.RoleDefinitionID) client := &http.Client{} req, err := http.NewRequest("PUT", url, bytes.NewBufferString(jsonString)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", "Go HTTP Client") defaultApiBearerToken, err := r.azAPIClient.GetDefaultAPIBearerToken() if err != nil { return err } // add bearer token to header req.Header.Add("Authorization", "Bearer "+defaultApiBearerToken) // make request resp, err := client.Do(req) if err != nil { return err } // read response body body, err := io.ReadAll(resp.Body) if err != nil { return err } log.Debugln(string(body)) if strings.Contains(string(body), "InvalidActionOrNotAction") { return fmt.Errorf("InvalidActionOrNotAction: %s", string(body)) } return nil } func (r *SPRoleAssignmentManager) AssignRoleToSP(subscription string, SPOBjectID string, role domain.Role) error { // scope := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscription, resourceGroupName) scope := fmt.Sprintf("/subscriptions/%s", subscription) url := fmt.Sprintf("https://management.azure.com/%s/providers/Microsoft.Authorization/roleAssignments/%s?api-version=2022-04-01", scope, uuid.New().String()) data := map[string]interface{}{ "principalId": SPOBjectID, "principalType": "ServicePrincipal", "roleDefinitionId": role.RoleDefinitionResourceID, } properties := map[string]interface{}{ "properties": data, } // marshal data as json jsonData, err := json.Marshal(properties) if err != nil { return err } //convert to json string jsonString := string(jsonData) log.Debugf("jsonString: %s", jsonString) client := &http.Client{} req, err := http.NewRequest("PUT", url, bytes.NewBufferString(jsonString)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", "Go HTTP Client") defaultApiBearerToken, err := r.azAPIClient.GetDefaultAPIBearerToken() if err != nil { return err } // add bearer token to header req.Header.Add("Authorization", "Bearer "+defaultApiBearerToken) // make request resp, err := client.Do(req) if err != nil { return err } // read response body body, err := io.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode != 200 && resp.StatusCode != 201 { return fmt.Errorf("Failed to assign role to SP. Status code: %s", string(body)) } // print response body log.Debugln(string(body)) return nil } // func (r *SPRoleAssignmentManager) AssignRoleToSP(scope string) error { // // armauthorization.NewClassicAdministratorsClient() // clientFactory, err := armauthorization.NewClientFactory(m.SubscriptionID, m.DefaultCred, nil) // if err != nil { // log.Fatalf("failed to create client: %v", err) // } // res, err := clientFactory.NewRoleAssignmentsClient().Create(m.Ctx, m.SubscriptionID, uuid.New().String(), armauthorization.RoleAssignmentCreateParameters{ // Properties: &armauthorization.RoleAssignmentProperties{ // PrincipalID: &m.SPObjectID, // PrincipalType: to.StringPtr(armauthorization.PrincipalTypeServicePrincipal), // RoleDefinitionID: &m.RoleDefinitionResourceID, // }, // }, nil) // // rac, err := armauthorization.NewRoleAssignmentsClient(m.SubscriptionID, m.DefaultCred, nil) // // if err != nil { // // return err // // } // // rao := armauthorization.RoleAssignmentsClientCreateOptions{ // // } // // roleAssignmentParams := authorization.RoleAssignmentCreateParameters{ // // Properties: &authorization.RoleAssignmentProperties{ // // PrincipalID: &m.SPObjectID, // // RoleDefinitionID: &m.RoleDefinitionResourceID, // // }, // // } // // roleAssignmentParams := armauthorization.RoleAssignmentCreateParameters{ // // Properties: &armauthorization.RoleAssignmentProperties{ // // PrincipalID: &m.SPObjectID, // // RoleDefinitionID: &m.RoleDefinitionResourceID, // // }, // // } // _, err = rac.Create(m.Ctx, scope, uuid.New().String(), roleAssignmentParams, nil) // // _, err = m.RoleAssignmentsClient.Create(m.Ctx, scope, uuid.New().String(), roleAssignmentParams) // if err != nil { // if strings.Contains(err.Error(), "RoleAssignmentExists") { // log.Infoln("Role assignment already exists. Skipping...") // return nil // } // return err // } // if err != nil { // return err // } // return nil // }z // Initialise detachRolesFromSP detaches all roles from the SP func (r *SPRoleAssignmentManager) DetachRolesFromSP(ctx context.Context, subscription string, SPOBjectID string, role domain.Role) error { filter := fmt.Sprintf("assignedTo('%s')", SPOBjectID) resp, err := r.azAPIClient.RoleAssignmentsClient.List(ctx, filter) if err != nil { return err } roleAssignments := resp.Values() for _, roleAssignment := range roleAssignments { _, err := r.azAPIClient.RoleAssignmentsDeletionClient.DeleteByID(ctx, string(*roleAssignment.ID), nil) if err != nil { return err } } return nil } func (r *SPRoleAssignmentManager) DeleteCustomRole(subscription string, role domain.Role) error { url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s?api-version=2018-01-01-preview", subscription, role.RoleDefinitionID) client := &http.Client{} req, err := http.NewRequest("DELETE", url, nil) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", "Go HTTP Client") defaultApiBearerToken, err := r.azAPIClient.GetDefaultAPIBearerToken() if err != nil { return err } // add bearer token to header req.Header.Add("Authorization", "Bearer "+defaultApiBearerToken) // make request resp, err := client.Do(req) if err != nil { return err } // read response body body, err := io.ReadAll(resp.Body) if err != nil { log.Warnf("Could not delete role definition: %s\n", err) } log.Debugln(string(body)) log.Infoln("Role definition deleted successfully") return nil } func stringExistsInSlice(s string, sl []string) bool { for _, v := range sl { if v == s { return true } } return false } func filterInvalidActions(permissions []string, invalidActions []string) []string { var validPermissions []string for _, permission := range permissions { if !stringExistsInSlice(permission, invalidActions) { validPermissions = append(validPermissions, permission) } } return validPermissions }