pkg/domain/authorizationErrorParser.go (107 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 domain
import (
"errors"
"fmt"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
)
func GetScopePermissionsFromAuthError(authErrMesg string) (map[string][]string, error) {
log.Debugf("Attempting to Parse Authorization Error: %s", authErrMesg)
if authErrMesg != "" && !strings.Contains(authErrMesg, "AuthorizationFailed") && !strings.Contains(authErrMesg, "Authorization failed") && !strings.Contains(authErrMesg, "AuthorizationPermissionMismatch") && !strings.Contains(authErrMesg, "LinkedAccessCheckFailed") {
log.Warnln("Non Authorization Error when creating deployment:", authErrMesg)
return nil, errors.New("Could not parse deploment error, potentially due to a Non-Authorization error")
}
var resMap map[string][]string
var err error
switch {
case strings.Count(authErrMesg, "LinkedAuthorizationFailed") >= 1:
log.Debug("Parsing LinkedAuthorizationFailed Error")
resMap, err = parseLinkedAuthorizationFailedErrors(authErrMesg)
case strings.Count(authErrMesg, "AuthorizationFailed") >= 1:
log.Debug("Parsing AuthorizationFailed Error")
resMap, err = parseMultiAuthorizationFailedErrors(authErrMesg)
case strings.Count(authErrMesg, "Authorization failed") >= 1:
log.Debug("Parsing Authorization failed Error")
resMap, err = parseMultiAuthorizationErrors(authErrMesg)
case strings.Count(authErrMesg, "AuthorizationPermissionMismatch") >= 1:
log.Debug("Parsing AuthorizationPermissionMismatch Error")
resMap, err = parseAuthorizationPermissionMismatchError(authErrMesg)
case strings.Count(authErrMesg, "LinkedAccessCheckFailed") >= 1:
log.Debug("Parsing LinkedAccessCheckFailed Error")
resMap, err = parseLinkedAccessCheckFailedError(authErrMesg)
}
if err != nil {
return nil, err
}
// If map is empty, return error
if len(resMap) == 0 {
return nil, fmt.Errorf("Could not parse deployment error for scope/permissions: %s", authErrMesg)
}
return appendPermissionsForSpecialCases(resMap), nil
}
// For 'AuthorizationFailed' errors
func parseMultiAuthorizationFailedErrors(authorizationFailedErrMsg string) (map[string][]string, error) {
re := regexp.MustCompile(`The client '([^']+)' with object id '([^']+)' does not have authorization to perform action '([^']+)'.* over scope '([^']+)' or the scope is invalid\.`)
matches := re.FindAllStringSubmatch(authorizationFailedErrMsg, -1)
if len(matches) == 0 {
return nil, errors.New("No matches found in 'AuthorizationFailed' error message")
}
scopePermissionsMap := make(map[string][]string)
// Iterate through the matches and populate the map
for _, match := range matches {
if len(match) == 5 {
// resourceType := match[1]
action := match[3]
scope := match[4]
if _, ok := scopePermissionsMap[scope]; !ok {
scopePermissionsMap[scope] = make([]string, 0)
}
scopePermissionsMap[scope] = append(scopePermissionsMap[scope], action)
}
}
// if map is empty, return error
if len(scopePermissionsMap) == 0 {
return nil, errors.New("No scope/permissions found in Multi error message")
}
return scopePermissionsMap, nil
}
// For 'Authorization failed' errors
func parseMultiAuthorizationErrors(authorizationFailedErrMsg string) (map[string][]string, error) {
// Regular expression to extract resource information
re := regexp.MustCompile(`Authorization failed for template resource '([^']+)' of type '([^']+)'\. The client '([^']+)' with object id '([^']+)' does not have permission to perform action '([^']+)' at scope '([^']+)'\.`)
// Find all matches in the error message
matches := re.FindAllStringSubmatch(authorizationFailedErrMsg, -1)
// If No Matches found return error
if len(matches) == 0 {
return nil, errors.New("No matches found in 'Authorization failed' error message")
}
// Create a map to store scope/permissions
scopePermissionsMap := make(map[string][]string)
// Iterate through the matches and populate the map
for _, match := range matches {
if len(match) == 7 {
// resourceType := match[1]
action := match[5]
scope := match[6]
if _, ok := scopePermissionsMap[scope]; !ok {
scopePermissionsMap[scope] = make([]string, 0)
}
scopePermissionsMap[scope] = append(scopePermissionsMap[scope], action)
}
}
// if map is empty, return error
if len(scopePermissionsMap) == 0 {
return nil, errors.New("No scope/permissions found in Multi error message")
}
return scopePermissionsMap, nil
}
// For 'LinkedAuthorizationFailed' errors
func parseLinkedAuthorizationFailedErrors(authorizationFailedErrMsg string) (map[string][]string, error) {
// Regular expression to extract resource information
// re := regexp.MustCompile(`Authorization failed for template resource '([^']+)' of type '([^']+)'\. The client '([^']+)' with object id '([^']+)' does not have permission to perform action '([^']+)' at scope '([^']+)'\.`)
// Find regular expressions to pull action and scope from error message "does not have permission to perform action(s) 'Microsoft.Network/virtualNetworks/subnets/join/action' on the linked scope(s) '/subscriptions/SSSSSSSS-SSSS-SSSS-SSSS-SSSSSSSSSSSS/resourceGroups/az-mpf-tf-test-rg/providers/Microsoft.Network/virtualNetworks/vnet-32a70ccbb3247e2b/subnets/subnet-32a70ccbb3247e2b' (respectively) or the linked scope(s) are invalid".
re := regexp.MustCompile(`does not have permission to perform action\(s\) '([^']+)' on the linked scope\(s\) '([^']+)' \(respectively\) or the linked scope\(s\) are invalid`)
// Find all matches in the error message
matches := re.FindAllStringSubmatch(authorizationFailedErrMsg, -1)
// If No Matches found return error
if len(matches) == 0 {
return nil, errors.New("No matches found in 'Authorization failed' error message")
}
// Create a map to store scope/permissions
scopePermissionsMap := make(map[string][]string)
// Iterate through the matches and populate the map
for _, match := range matches {
if len(match) == 3 {
// resourceType := match[1]
action := match[1]
scope := match[2]
if _, ok := scopePermissionsMap[scope]; !ok {
scopePermissionsMap[scope] = make([]string, 0)
}
scopePermissionsMap[scope] = append(scopePermissionsMap[scope], action)
}
}
// if map is empty, return error
if len(scopePermissionsMap) == 0 {
return nil, errors.New("No scope/permissions found in Multi error message")
}
return scopePermissionsMap, nil
}