Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Azure plugin community gallery image rule #181

Merged
merged 7 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 10 additions & 25 deletions pkg/components/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,8 @@ func NewValidatorConfig() *ValidatorConfig {
Validator: &aws.AwsValidatorSpec{},
},
AzurePlugin: &AzurePluginConfig{
Release: &validator.HelmRelease{},
Validator: &azure.AzureValidatorSpec{},
RuleTypes: make(map[int]string),
StaticDeploymentTypes: make(map[int]string),
StaticDeploymentValues: make(map[int]*AzureStaticDeploymentValues),
Release: &validator.HelmRelease{},
Validator: &azure.AzureValidatorSpec{},
},
MaasPlugin: &MaasPluginConfig{
Release: &validator.HelmRelease{},
Expand Down Expand Up @@ -372,17 +369,14 @@ func (c *AWSPluginConfig) decrypt() error {

// AzurePluginConfig represents the Azure plugin configuration.
type AzurePluginConfig struct {
Enabled bool `yaml:"enabled"`
Release *validator.HelmRelease `yaml:"helmRelease"`
ServiceAccountName string `yaml:"serviceAccountName,omitempty"`
Cloud string `yaml:"cloud"`
TenantID string `yaml:"tenantId"`
ClientID string `yaml:"clientId"`
ClientSecret string `yaml:"clientSecret"`
RuleTypes map[int]string `yaml:"ruleTypes"`
StaticDeploymentTypes map[int]string `yaml:"staticDeploymentTypes"`
StaticDeploymentValues map[int]*AzureStaticDeploymentValues `yaml:"staticDeploymentValues"`
Validator *azure.AzureValidatorSpec `yaml:"validator"`
Enabled bool `yaml:"enabled"`
Release *validator.HelmRelease `yaml:"helmRelease"`
ServiceAccountName string `yaml:"serviceAccountName,omitempty"`
Cloud string `yaml:"cloud"`
TenantID string `yaml:"tenantId"`
ClientID string `yaml:"clientId"`
ClientSecret string `yaml:"clientSecret"`
Validator *azure.AzureValidatorSpec `yaml:"validator"`
}

func (c *AzurePluginConfig) encrypt() error {
Expand All @@ -405,15 +399,6 @@ func (c *AzurePluginConfig) decrypt() error {
return nil
}

// AzureStaticDeploymentValues represents the static deployment values for Azure.
type AzureStaticDeploymentValues struct {
Subscription string `yaml:"subscriptionUuid"`
ResourceGroup string `yaml:"resourceGroupUuid"`
VirtualNetwork string `yaml:"virtualNetworkUuid"`
Subnet string `yaml:"subnetUuid"`
ComputeGallery string `yaml:"computeGalleryUuid"`
}

// MaasPluginConfig represents the MAAS plugin configuration.
type MaasPluginConfig struct {
Enabled bool `yaml:"enabled"`
Expand Down
245 changes: 169 additions & 76 deletions pkg/services/validator/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"time"

Expand All @@ -22,21 +23,25 @@
)

const (
ruleTypeRBAC = "RBAC"

mustBeValidUUID = "must be valid UUID"

azureNoCredsErr = "DefaultAzureCredential: "

mockAzureScope = "00000000-0000-0000-0000-000000000000"

mockAzureRoleAssignment = "00000000-000000-0000000000"

regexOneCharString = ".+"
)

var (
azureSecretName = "azure-creds"
)

type azureRule interface {
*plug.RBACRule | *plug.CommunityGalleryImageRule
}

func readAzurePlugin(vc *components.ValidatorConfig, tc *cfg.TaskConfig, k8sClient kubernetes.Interface) error {
c := vc.AzurePlugin

Expand All @@ -62,16 +67,21 @@
// readAzurePluginRules reads Azure plugin configuration and rules from the user.
func readAzurePluginRules(vc *components.ValidatorConfig, _ *cfg.TaskConfig, _ kubernetes.Interface) error {
log.Header("Azure Plugin Rule Configuration")
// Configure RBAC rules. Unlike how other plugins are styled, no prompt for whether the user
// wants to configure this rule type because right now it is the only rule type for the plugin.
if err := configureAzureRBACRules(vc.AzurePlugin); err != nil {
return fmt.Errorf("failed to configure RBAC rules: %w", err)

c := vc.AzurePlugin
ruleNames := make([]string, 0)

if err := configureRBACRules(c, &ruleNames); err != nil {
return err

Check warning on line 75 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L75

Added line #L75 was not covered by tests
}
if err := configureCommunityGalleryImageRules(c, &ruleNames); err != nil {
return err

Check warning on line 78 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L78

Added line #L78 was not covered by tests
}

if c.Validator.ResultCount() == 0 {
return errNoRulesEnabled

Check warning on line 82 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L82

Added line #L82 was not covered by tests
}

// impossible at present. uncomment if/when additional azure rule types are added.
// if c.Validator.ResultCount() == 0 {
// return errNoRulesEnabled
// }
return nil
}

Expand Down Expand Up @@ -193,25 +203,43 @@
return nil
}

// configureAzureRBACRules sets up zero or more RBAC rules based on pre-existing files or user
// input.
func configureAzureRBACRules(c *components.AzurePluginConfig) error {
var err error
addRules := true
ruleNames := make([]string, 0)
func initAzureRule[R azureRule](r R, ruleType string, ruleNames *[]string) error {
name := reflect.ValueOf(r).Elem().FieldByName("Name").String()
if name != "" {
log.InfoCLI("Reconfiguring %s rule: %s", ruleType, name)
*ruleNames = append(*ruleNames, name)

Check warning on line 210 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L209-L210

Added lines #L209 - L210 were not covered by tests
} else {
name, err := getRuleName(ruleNames)
if err != nil {
return err

Check warning on line 214 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L214

Added line #L214 was not covered by tests
}
reflect.ValueOf(r).Elem().FieldByName("Name").Set(reflect.ValueOf(name))
}
return nil
}

// configureRBACRules sets up zero or more RBAC rules based on pre-existing files or user input.
// nolint:dupl
func configureRBACRules(c *components.AzurePluginConfig, ruleNames *[]string) error {
log.InfoCLI(`
RBAC validation rules ensure that security principals have the required permissions.
`)

validateRBAC, err := prompts.ReadBool("Enable Azure RBAC validation", true)
if err != nil {
return err

Check warning on line 230 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L230

Added line #L230 was not covered by tests
}
if !validateRBAC {
c.Validator.RBACRules = nil
return nil

Check warning on line 234 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L233-L234

Added lines #L233 - L234 were not covered by tests
}
for i, r := range c.Validator.RBACRules {
r := r
ruleType := c.RuleTypes[i]
log.InfoCLI("Reconfiguring Azure RBAC %s rule: %s", ruleType, r.Name)

if err = configureAzureRBACRule(&ruleNames, &r); err != nil {
return fmt.Errorf("failed to configure RBAC rule: %w", err)
if err := readRBACRule(c, &r, i, ruleNames); err != nil {
return err

Check warning on line 239 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L238-L239

Added lines #L238 - L239 were not covered by tests
}

c.Validator.RBACRules[i] = r
}

addRules := true
if len(c.Validator.RBACRules) == 0 {
c.Validator.RBACRules = make([]plug.RBACRule, 0)
} else {
Expand All @@ -223,82 +251,53 @@
if !addRules {
return nil
}

log.InfoCLI("Note: You must configure at least one rule for plugin configuration.")
ruleIdx := len(c.Validator.RBACRules)

for {
log.InfoCLI("Note: Collecting input for rule #%d", ruleIdx+1)

// This is intentional. We only have one rule type in the Azure plugin now, so we don't
// prompt the user for rule type. But we are keeping the rest of the type-oriented code here
// (e.g. the rule types tracked in the YAML config file) to avoid less refactor work later
// when we add more rule types for Azure.
ruleType := ruleTypeRBAC
c.RuleTypes[ruleIdx] = ruleType

rule := &plug.RBACRule{
Permissions: []plug.PermissionSet{},
}

switch ruleType {
case ruleTypeRBAC:
if err := configureAzureRBACRule(&ruleNames, rule); err != nil {
return fmt.Errorf("failed to configure RBAC rule: %w", err)
}
default:
return fmt.Errorf("unknown rule type (%s)", ruleType)
if err := readRBACRule(c, nil, -1, ruleNames); err != nil {
return err

Check warning on line 256 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L256

Added line #L256 was not covered by tests
}

c.Validator.RBACRules = append(c.Validator.RBACRules, *rule)

addRBACRule, err := prompts.ReadBool("Add additional RBAC rule", false)
add, err := prompts.ReadBool("Add additional RBAC rule", false)
if err != nil {
return fmt.Errorf("failed to prompt for bool for add an RBAC rule: %w", err)
return err

Check warning on line 260 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L260

Added line #L260 was not covered by tests
}
if !addRBACRule {
if !add {
break
}
ruleIdx++
}

return nil
}

func initRbacRule(ruleNames *[]string, r *plug.RBACRule) error {
var err error
if r.Name != "" {
log.InfoCLI("Reconfiguring RBAC rule: %s", r.Name)
*ruleNames = append(*ruleNames, r.Name)
} else {
r.Name, err = getRuleName(ruleNames)
if err != nil {
return err
}
// readRBACRule begins the process of reconfiguring or beginning a new RBAC rule.
func readRBACRule(c *components.AzurePluginConfig, r *plug.RBACRule, idx int, ruleNames *[]string) error {
if r == nil {
r = &plug.RBACRule{}
}
return nil
}

// Allows the user to configure an Azure RBAC rule where they specify every detail.
func configureAzureRBACRule(ruleNames *[]string, r *plug.RBACRule) error {
err := initRbacRule(ruleNames, r)
err := initAzureRule(r, "RBAC", ruleNames)
if err != nil {
return err
}

logToCollect("security principal", formatAzureGUID)
r.PrincipalID, err = prompts.ReadTextRegex("Security principal", r.PrincipalID, mustBeValidUUID, prompts.UUIDRegex)
if err != nil {
return fmt.Errorf("failed to prompt for text for service principal: %w", err)
return err

Check warning on line 283 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L283

Added line #L283 was not covered by tests
}

if err := readRBACRulePermissionSets(r); err != nil {
return err

Check warning on line 287 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L287

Added line #L287 was not covered by tests
}

if err := configureAzureRBACRulePermissionSets(r); err != nil {
return fmt.Errorf("failed to configure permission sets: %w", err)
if idx == -1 {
c.Validator.RBACRules = append(c.Validator.RBACRules, *r)
} else {
c.Validator.RBACRules[idx] = *r

Check warning on line 293 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L293

Added line #L293 was not covered by tests
}
return nil
}

func configureAzureRBACRulePermissionSets(r *plug.RBACRule) error {
// readRBACRulePermissionSets begins the process of beginning a new list of permission sets. Users
// can provide input via file or prompts.
func readRBACRulePermissionSets(r *plug.RBACRule) error {
log.InfoCLI("Note: You must configure at least one permission set for rule.")
log.InfoCLI("If you're updating an existing RBAC rule, its permission sets will be replaced.")

Expand Down Expand Up @@ -347,8 +346,99 @@
}
}

// configureCommunityGalleryImageRules sets up zero or more Community Gallery Image rules based on
// pre-existing files or user input.
func configureCommunityGalleryImageRules(c *components.AzurePluginConfig, ruleNames *[]string) error {
log.InfoCLI(`
Community gallery image validation rules ensure that images are publicly available via community galleries.
`)

validateCommunityGalleryImage, err := prompts.ReadBool("Enable Community Gallery Image validation", true)
if err != nil {
return err

Check warning on line 358 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L358

Added line #L358 was not covered by tests
}
if !validateCommunityGalleryImage {
c.Validator.CommunityGalleryImageRules = nil

Check warning on line 361 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L361

Added line #L361 was not covered by tests
}
for i, r := range c.Validator.CommunityGalleryImageRules {
r := r
if err := readCommunityGalleryImageRule(c, &r, i, ruleNames); err != nil {
return err

Check warning on line 366 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L364-L366

Added lines #L364 - L366 were not covered by tests
}
}
addRules := true
if c.Validator.CommunityGalleryImageRules == nil {
c.Validator.CommunityGalleryImageRules = make([]plug.CommunityGalleryImageRule, 0)
} else {
addRules, err = prompts.ReadBool("Add another Community Gallery Image rule", false)
if err != nil {
return err

Check warning on line 375 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L373-L375

Added lines #L373 - L375 were not covered by tests
}
}
if !addRules {
return nil

Check warning on line 379 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L379

Added line #L379 was not covered by tests
}
for {
if err := readCommunityGalleryImageRule(c, nil, -1, ruleNames); err != nil {
return err

Check warning on line 383 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L383

Added line #L383 was not covered by tests
}
add, err := prompts.ReadBool("Add additional Community Gallery Image rule", false)
if err != nil {
return err

Check warning on line 387 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L387

Added line #L387 was not covered by tests
}
if !add {
break
}
}
return nil
}

// readCommunityGalleryImageRule begins the process of reconfiguring or beginning a new Community
// Gallery Image rule.
func readCommunityGalleryImageRule(c *components.AzurePluginConfig, r *plug.CommunityGalleryImageRule, idx int, ruleNames *[]string) error {
if r == nil {
r = &plug.CommunityGalleryImageRule{}
}

err := initAzureRule(r, "Community Gallery Image", ruleNames)
if err != nil {
return err

Check warning on line 405 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L405

Added line #L405 was not covered by tests
}

logToCollect("gallery location", formatAzureLocation)
if r.Gallery.Location, err = prompts.ReadText("Gallery location", r.Gallery.Location, false, -1); err != nil {
return err

Check warning on line 410 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L410

Added line #L410 was not covered by tests
}

if r.Gallery.Name, err = prompts.ReadText("Gallery name", r.Gallery.Name, false, -1); err != nil {
return err

Check warning on line 414 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L414

Added line #L414 was not covered by tests
}

if r.Images, err = prompts.ReadTextSlice("Images", strings.Join(r.Images, "\n"), "image names must be at least one character", regexOneCharString, false); err != nil {
return fmt.Errorf("failed to prompt for images: %w", err)

Check warning on line 418 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L418

Added line #L418 was not covered by tests
}

log.InfoCLI(`
Community gallery images are accessed via subscriptions.
Provide the ID of the subscription you want to verify can access the community gallery image(s).
This can be any subscription the security principal you authed the Azure plugin with has access to.
`)
logToCollect("subscription ID", formatAzureGUID)
if r.SubscriptionID, err = prompts.ReadTextRegex("Subscription ID", r.SubscriptionID, mustBeValidUUID, prompts.UUIDRegex); err != nil {
return err

Check warning on line 428 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L428

Added line #L428 was not covered by tests
}

if idx == -1 {
c.Validator.CommunityGalleryImageRules = append(c.Validator.CommunityGalleryImageRules, *r)
} else {
c.Validator.CommunityGalleryImageRules[idx] = *r

Check warning on line 434 in pkg/services/validator/azure.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/validator/azure.go#L434

Added line #L434 was not covered by tests
}
return nil
}

const (
formatAzureGUID = iota
formatAzureGUID = iota
formatAzureLocation = iota
)

// logToCollect logs a few messages to guide the user when we need to collect data from them.
Expand All @@ -367,6 +457,9 @@
case formatAzureGUID:
formatLabel = "Azure GUID"
example = exampleGUID
case formatAzureLocation:
formatLabel = "Azure location"
example = "westus"
}

log.InfoCLI("Format: %s", formatLabel)
Expand Down
Loading