pkg/usecase/mpfService.go (162 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 usecase
import (
"context"
"strings"
"github.com/Azure/mpf/pkg/domain"
log "github.com/sirupsen/logrus"
)
// RetryDeploymentResponseErrorMessage is the error message returned by a deployment authorization checker when it wants the deployment to be retried
const RetryDeploymentResponseErrorMessage = "RetryGetDeploymentAuthorizationErrors"
type MPFService struct {
ctx context.Context
rgManager ResourceGroupManager
spRoleAssignmentManager ServicePrincipalRolemAssignmentManager
deploymentAuthCheckerCleaner DeploymentAuthorizationCheckerCleaner
mpfConfig domain.MPFConfig
initialPermissionsToAdd []string
permissionsToAddToResult []string
requiredPermissions map[string][]string
autoAddReadPermissionForEachWrite bool
autoAddDeletePermissionForEachWrite bool
autoCreateResourceGroup bool
}
func NewMPFService(ctx context.Context, rgMgr ResourceGroupManager, spRoleAssgnMgr ServicePrincipalRolemAssignmentManager, deploymentAuthChkCln DeploymentAuthorizationCheckerCleaner, mpfConfig domain.MPFConfig, initialPermissionsToAdd []string, permissionsToAddToResult []string, autoAddReadPermissionForEachWrite bool, autoAddDeletePermissionForEachWrite bool, autoCreateResourceGroup bool) *MPFService {
return &MPFService{
ctx: ctx,
rgManager: rgMgr,
spRoleAssignmentManager: spRoleAssgnMgr,
deploymentAuthCheckerCleaner: deploymentAuthChkCln,
mpfConfig: mpfConfig,
initialPermissionsToAdd: initialPermissionsToAdd,
permissionsToAddToResult: permissionsToAddToResult,
requiredPermissions: make(map[string][]string),
autoAddReadPermissionForEachWrite: autoAddReadPermissionForEachWrite,
autoAddDeletePermissionForEachWrite: autoAddDeletePermissionForEachWrite,
autoCreateResourceGroup: autoCreateResourceGroup,
}
}
func (s *MPFService) returnMPFResult(err error) (domain.MPFResult, error) {
mpfResult := domain.GetMPFResult(s.requiredPermissions)
if err != nil && len(mpfResult.RequiredPermissions) == 0 {
return domain.MPFResult{}, err
}
if err != nil && len(mpfResult.RequiredPermissions) > 0 {
return mpfResult, err
}
return mpfResult, nil
}
func (s *MPFService) GetMinimumPermissionsRequired() (domain.MPFResult, error) {
if s.autoCreateResourceGroup {
// Create Resource Group
log.Infof("Creating Resource Group: %s \n", s.mpfConfig.ResourceGroup.ResourceGroupName)
err := s.rgManager.CreateResourceGroup(s.ctx, s.mpfConfig.ResourceGroup.ResourceGroupName, s.mpfConfig.ResourceGroup.Location)
if err != nil {
log.Fatal(err)
}
log.Infof("Resource Group: %s created successfully \n", s.mpfConfig.ResourceGroup.ResourceGroupName)
// defer s.deploymentAuthCheckerCleaner.CleanDeployment(s.mpfConfig)
}
defer s.CleanUpResources()
// Delete all existing role assignments for the service principal
err := s.spRoleAssignmentManager.DetachRolesFromSP(s.ctx, s.mpfConfig.SubscriptionID, s.mpfConfig.SP.SPObjectID, s.mpfConfig.Role)
if err != nil {
log.Warnf("Unable to delete Role Assignments: %v\n", err)
return s.returnMPFResult(err)
}
log.Info("Deleted all existing role assignments for service principal \n")
// Initialize new custom role
log.Infoln("Initializing Custom Role")
// err = mpf.CreateUpdateCustomRole([]string{})
err = s.spRoleAssignmentManager.CreateUpdateCustomRole(s.mpfConfig.SubscriptionID, s.mpfConfig.Role, s.initialPermissionsToAdd)
if err != nil {
log.Warn(err)
return s.returnMPFResult(err)
}
log.Infoln("Custom role initialized successfully")
// Assign new custom role to service principal
log.Infoln("Assigning new custom role to service principal")
// err = mpf.AssignRoleToSP()
err = s.spRoleAssignmentManager.AssignRoleToSP(s.mpfConfig.SubscriptionID, s.mpfConfig.SP.SPObjectID, s.mpfConfig.Role)
if err != nil {
log.Warn(err)
return s.returnMPFResult(err)
}
log.Infoln("New Custom Role assigned to service principal successfully")
// Add initial permissions to requiredPermissions map
log.Infoln("Adding initial permissions to requiredPermissions map")
s.requiredPermissions[s.mpfConfig.SubscriptionID] = append(s.requiredPermissions[s.mpfConfig.SubscriptionID], s.permissionsToAddToResult...)
maxIterations := 50
iterCount := 0
for {
authErrMesg, err := s.deploymentAuthCheckerCleaner.GetDeploymentAuthorizationErrors(s.mpfConfig)
log.Infof("Iteration Number: %d \n", iterCount)
if authErrMesg == "" && err == nil {
log.Infoln("Authorization Successful")
break
}
log.Debugln("authErrMesg: ", authErrMesg)
if err == nil && strings.Contains(authErrMesg, RetryDeploymentResponseErrorMessage) {
log.Warnf("received retry request from authorization checker, retrying deployment.... \n")
continue
}
if err != nil {
log.Warnf("Non Authorization error received: %v \n", err)
return s.returnMPFResult(err)
}
log.Debugln("Deployment Authorization Error:", authErrMesg)
scpMp, err := domain.GetScopePermissionsFromAuthError(authErrMesg)
if err != nil {
log.Warnf("Could Not Parse Deployment Authorization Error: %v \n", err)
return s.returnMPFResult(err)
}
log.Infoln("Successfully Parsed Deployment Authorization Error")
log.Debugln("scope permissions found from deployment error:", scpMp)
// auto add read and delete permissions as per configuration
for scope, permissions := range scpMp {
for _, permission := range permissions {
if s.autoAddReadPermissionForEachWrite && strings.HasSuffix(permission, "/write") {
readPermission := strings.Replace(permission, "/write", "/read", 1)
scpMp[scope] = append(scpMp[scope], readPermission)
}
if s.autoAddDeletePermissionForEachWrite && strings.HasSuffix(permission, "/write") {
deletePermission := strings.Replace(permission, "/write", "/delete", 1)
scpMp[scope] = append(scpMp[scope], deletePermission)
}
}
}
log.Infoln("Adding mising scopes/permissions to final result map...")
for k, v := range scpMp {
s.requiredPermissions[k] = append(s.requiredPermissions[k], v...)
s.requiredPermissions[s.mpfConfig.SubscriptionID] = append(s.requiredPermissions[s.mpfConfig.SubscriptionID], v...)
}
// assign permission to role
log.Infoln("Adding permission/scope to role...........")
log.Debugln("Number of Permissions added to role:", len(s.requiredPermissions[s.mpfConfig.SubscriptionID]))
permissionsIncludingInitialPermissions := append(s.initialPermissionsToAdd, s.requiredPermissions[s.mpfConfig.SubscriptionID]...)
err = s.spRoleAssignmentManager.CreateUpdateCustomRole(s.mpfConfig.SubscriptionID, s.mpfConfig.Role, permissionsIncludingInitialPermissions)
// err = s.spRoleAssignmentManager.CreateUpdateCustomRole(s.mpfConfig.SubscriptionID, s.mpfConfig.ResourceGroup.ResourceGroupName, s.mpfConfig.Role, s.requiredPermissions[s.mpfConfig.ResourceGroup.ResourceGroupResourceID])
if err != nil {
log.Infoln("Error when adding permission/scope to role: \n", err)
log.Warn(err)
return s.returnMPFResult(err)
}
log.Infoln("Permission/scope added to role successfully")
iterCount++
if iterCount == maxIterations {
log.Warnln("max iterations for fetching authorization errors reached, exiting...")
return s.returnMPFResult(err)
}
}
return s.returnMPFResult(nil)
}
func (s *MPFService) CleanUpResources() {
log.Infoln("Cleaning up resources...")
log.Infoln("*************************")
// Cancel deployment. Even if cancelling deployment fails attempt to delete other resources
// _ = m.CancelDeployment(deploymentName)
err := s.deploymentAuthCheckerCleaner.CleanDeployment(s.mpfConfig)
if err != nil {
log.Warnln("Cleaning up deployment returned an error, attempting to clean rest of the resources")
}
// Detach Roles from SP
err = s.spRoleAssignmentManager.DetachRolesFromSP(s.ctx, s.mpfConfig.SubscriptionID, s.mpfConfig.SP.SPObjectID, s.mpfConfig.Role)
if err != nil {
log.Warnf("Could not detach roles from SP: %s\n", err)
}
// Delete Custom Role
err = s.spRoleAssignmentManager.DeleteCustomRole(s.mpfConfig.SubscriptionID, s.mpfConfig.Role)
if err != nil {
log.Warnf("Could not delete custom role: %s\n", err)
}
// Delete Resource Group
if s.autoCreateResourceGroup {
err = s.rgManager.DeleteResourceGroup(s.ctx, s.mpfConfig.ResourceGroup.ResourceGroupName)
if err != nil {
log.Warnf("Error when deleting resource group: %s \n", err)
}
log.Infoln("Resource group deletion initiated successfully...")
}
}