assets/policyDefinition.go (129 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package assets
import (
"encoding/json"
"errors"
"fmt"
"github.com/Azure/alzlib/to"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armpolicy"
)
type PolicyDefinition struct {
armpolicy.Definition
}
func NewPolicyDefinition(pd armpolicy.Definition) *PolicyDefinition {
return &PolicyDefinition{pd}
}
// policyDefinitionRule represents the opinionated rule section of a policy definition.
// This is used to determine the role assignments that need to be created,
// therefore we only care about the `then.details.roleDefinitionIds` field.
type policyDefinitionRule struct {
Then *struct {
Details *struct {
RoleDefinitionIds []string `json:"roleDefinitionIds,omitempty"`
} `json:"details"`
} `json:"then"`
}
// RoleDefinitionResourceIds returns the role definition ids referenced in a policy definition
// if they exist.
// We marshall the policyRule as JSON and then unmarshal into a custom type.
func (pd *PolicyDefinition) RoleDefinitionResourceIds() ([]string, error) {
if pd == nil || pd.Properties == nil || pd.Properties.PolicyRule == nil {
return nil, errors.New("PolicyDefinition.RoleDefinitionResourceIds: policy definition is nil, missing properties or policy rule")
}
j, err := json.Marshal(pd.Properties.PolicyRule)
if err != nil {
return nil, fmt.Errorf("PolicyDefinition.RoleDefinitionResourceIds: could not marshal policy rule: %w", err)
}
r := new(policyDefinitionRule)
if err := json.Unmarshal(j, r); err != nil {
// For append policies, the `then.details` field is an array, so we need to handle this case.
// There are no roleDefinitionIds here anyway, so we can just return an empty slice.
// This explains why the PolicyRule field if of type any.
jsonerr := new(json.UnmarshalTypeError)
if errors.As(err, &jsonerr) {
if jsonerr.Value == "array" && jsonerr.Field == "then.details" {
return []string{}, nil
}
}
return nil, fmt.Errorf("PolicyDefinition.RoleDefinitionResourceIds: could not unmarshal policy rule: %w", err)
}
if r.Then.Details == nil || r.Then.Details.RoleDefinitionIds == nil || len(r.Then.Details.RoleDefinitionIds) == 0 {
return []string{}, nil
}
return r.Then.Details.RoleDefinitionIds, nil
}
func (pd *PolicyDefinition) NormalizedRoleDefinitionResourceIds() ([]string, error) {
rdids, err := pd.RoleDefinitionResourceIds()
if err != nil {
return nil, err
}
normalized := make([]string, len(rdids))
for i, rdid := range rdids {
nrdid, err := normalizeRoleDefinitionId(rdid)
if err != nil {
return nil, err
}
normalized[i] = nrdid
}
return normalized, nil
}
func (pd *PolicyDefinition) AssignPermissionsParameterNames() ([]string, error) {
if pd == nil || pd.Properties == nil || pd.Properties.Parameters == nil {
return nil, errors.New("PolicyDefinition.AssignPermissionsParameterNames: policy definition is nil, missing properties or parameters")
}
names := make([]string, 0)
for name, param := range pd.Properties.Parameters {
if param.Metadata == nil || param.Metadata.AssignPermissions == nil || !*param.Metadata.AssignPermissions {
continue
}
names = append(names, name)
}
return names, nil
}
func (pd *PolicyDefinition) ParameterIsOptional(name string) (bool, error) {
if pd == nil || pd.Properties == nil || pd.Properties.Parameters == nil {
return false, errors.New("PolicyDefinition.ParameterIsOptional: policy definition is nil, missing properties or parameters")
}
param, ok := pd.Properties.Parameters[name]
if !ok {
return false, fmt.Errorf("PolicyDefinition.ParameterIsOptional: parameter %s not found in policy definition", name)
}
if param.DefaultValue == nil {
return false, nil
}
return true, nil
}
func (pd *PolicyDefinition) Parameter(name string) *armpolicy.ParameterDefinitionsValue {
if pd == nil || pd.Properties == nil || pd.Properties.Parameters == nil {
return nil
}
ret, ok := pd.Properties.Parameters[name]
if !ok {
return nil
}
return ret
}
// SetAssignPermissionsOnParameter sets the AssignPermissions metadata field to true for the parameter with the given name.
func (pd *PolicyDefinition) SetAssignPermissionsOnParameter(parameterName string) {
if pd == nil || pd.Properties == nil || pd.Properties.Parameters == nil {
return
}
param, ok := pd.Properties.Parameters[parameterName]
if !ok {
return
}
if param.Metadata == nil {
param.Metadata = new(armpolicy.ParameterDefinitionsValueMetadata)
}
param.Metadata.AssignPermissions = to.Ptr(true)
}
// UnsetAssignPermissionsOnParameter removes the AssignPermissions metadata field for the parameter with the given name.
func (pd *PolicyDefinition) UnsetAssignPermissionsOnParameter(parameterName string) {
if pd == nil || pd.Properties == nil || pd.Properties.Parameters == nil {
return
}
param, ok := pd.Properties.Parameters[parameterName]
if !ok {
return
}
if param.Metadata == nil {
return
}
param.Metadata.AssignPermissions = nil
}
// normalizeRoleDefinitionId takes a Azure builtin role definition id and returns a normalized id.
// This is one without the management group portion.
func normalizeRoleDefinitionId(id string) (string, error) {
resId, err := arm.ParseResourceID(id)
if err != nil {
return "", fmt.Errorf("normalizeRoleDefinitionId: could not parse resource id: %w", err)
}
return fmt.Sprintf("/providers/Microsoft.Authorization/roleDefinitions/%s", resId.Name), nil
}