Skip to content

Commit

Permalink
feat: read vCenter privileges from local file or editor (#152)
Browse files Browse the repository at this point in the history
## Issue
Resolves #99

## Description
Rather than prompting users to select individual vCenter privileges,
allow them to either specify a local file containing a single privilege
per line or edit a file that's pre-populated with all valid privileges.

---------

Signed-off-by: Tyler Gillson <tyler.gillson@gmail.com>
  • Loading branch information
TylerGillson authored and arturshadnik committed Aug 10, 2024
1 parent 3b2e499 commit 039e520
Show file tree
Hide file tree
Showing 13 changed files with 669 additions and 591 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ _build/
.idea/modules.xml
.idea/workspace.xml
.vscode
!.vscode/launch.json
*__debug_bin*

# Binaries
Expand Down
33 changes: 33 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/validator.go",
"args": [
"check", "-d", "-f", "/Users/tylergillson/.validator/validator-20240804075702/validator.yaml"
],
"env": {
"CLI_VERSION": "0.0.4-dev",
}
},
{
"name": "Integration Test",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}/tests/integration/suite_test.go",
"env": {
"DISABLE_KIND_CLUSTER_CHECK": "true",
"KUBECONFIG": "/Users/tylergillson/Downloads/vdev.kubeconfig",
"CLI_VERSION": "0.0.4-dev",
}
}
]
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/pterm/pterm v0.12.79
github.com/sirupsen/logrus v1.9.3
github.com/spectrocloud-labs/embeddedfs v0.1.0
github.com/spectrocloud-labs/prompts-tui v0.1.0
github.com/spectrocloud-labs/prompts-tui v0.1.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/validator-labs/validator v0.1.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spectrocloud-labs/embeddedfs v0.1.0 h1:Izs9wPYLVp8Fp9mi9zYysu9AzvHK1kIelQz3IIfh4N0=
github.com/spectrocloud-labs/embeddedfs v0.1.0/go.mod h1:JrCbGXImUCsim3jjYSahRJUKyVN57Fb5u3DkE3crqA4=
github.com/spectrocloud-labs/prompts-tui v0.1.0 h1:8UXUbNZNoEDNZYxAFViCnxznfPRhpS1zhgRWsRN3zOc=
github.com/spectrocloud-labs/prompts-tui v0.1.0/go.mod h1:XCvyEc3OLxKVXNLbOGZJOR6PiktfWqjYdrwU+ymCmLQ=
github.com/spectrocloud-labs/prompts-tui v0.1.1 h1:jNYFt6UzrSEc8K6GXyRenH1jzKbHwJbCCGMYtYYXKUo=
github.com/spectrocloud-labs/prompts-tui v0.1.1/go.mod h1:XCvyEc3OLxKVXNLbOGZJOR6PiktfWqjYdrwU+ymCmLQ=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
Expand Down
2 changes: 0 additions & 2 deletions pkg/components/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ func NewValidatorConfig() *ValidatorConfig {
Release: &validator.HelmRelease{},
Validator: &azure.AzureValidatorSpec{},
RuleTypes: make(map[int]string),
PlacementTypes: make(map[int]string),
StaticDeploymentTypes: make(map[int]string),
StaticDeploymentValues: make(map[int]*AzureStaticDeploymentValues),
},
Expand Down Expand Up @@ -381,7 +380,6 @@ type AzurePluginConfig struct {
ClientID string `yaml:"clientId"`
ClientSecret string `yaml:"clientSecret"`
RuleTypes map[int]string `yaml:"ruleTypes"`
PlacementTypes map[int]string `yaml:"placementTypes"`
StaticDeploymentTypes map[int]string `yaml:"staticDeploymentTypes"`
StaticDeploymentValues map[int]*AzureStaticDeploymentValues `yaml:"staticDeploymentValues"`
Validator *azure.AzureValidatorSpec `yaml:"validator"`
Expand Down
15 changes: 6 additions & 9 deletions pkg/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const (
KindImageTag = "v1.30.2"
NoProxyPrompt = "# Default NO_PROXY values are on the lines below.\n# Edit as you see fit (comments are ignored). The file should contain a list of NO_PROXY values, newline separated.\n# Type :wq to save and exit (if using vi).\n\n"

LocalFilepath = "Local Filepath"
FileEditor = "File Editor"

// Validator constants
ValidatorConfigFile = "validator.yaml"
ValidatorKindClusterName = "validator-kind-cluster"
Expand Down Expand Up @@ -50,11 +53,9 @@ const (
ValidatorVsphereVersionConstraint = ">= 6.0, < 9.0"
ValidatorVspherePrivilegeFile = "vsphere-root-level-privileges-all.yaml"

AWSPolicyDocumentPrompt = "# Provide the AWS policy document for IAM validation rule. The policy document should be in JSON format. Type :wq to save and exit (if using vi).\n"

AWSPolicyDocumentPrompt = "# Provide the AWS policy document for IAM validation rule. The policy document should be in JSON format. Type :wq to save and exit (if using vi).\n"
AzurePermissionSetPrompt = "# Provide the Azure permission set for IAM validation rule. The permission set should be in JSON format. Type :wq to save and exit (if using vi).\n"

DefaultStorageClassAnnotation string = "storageclass.kubernetes.io/is-default-class"
VcenterPrivilegePrompt = "# All valid vCenter privileges are on the lines below.\n# Edit as you see fit (comments are ignored). The file should contain a list of privileges, newline separated.\n# Type :wq to save and exit (if using vi).\n\n"

// Embed dirs
Kind string = "kind"
Expand All @@ -77,6 +78,7 @@ var (
HTTPSchemes = []string{"https://", "http://"}
RegistryMirrors = []string{"docker.io", "gcr.io", "ghcr.io", "k8s.gcr.io", "registry.k8s.io", "quay.io", "*"}
RegistryMirrorSeparator = "::"
FileInputs = []string{LocalFilepath, FileEditor}

// Command dirs
ValidatorSubdirs = []string{"logs", "manifests"}
Expand All @@ -85,11 +87,6 @@ var (
ValidatorImagePath = func() string {
return ValidatorImageRegistry + "/" + ValidatorImageRepository
}

PlacementTypeStatic = "Static"
PlacementTypeDynamic = "Dynamic"
PlacementTypes = []string{PlacementTypeStatic, PlacementTypeDynamic}

ValidatorWaitCmd = []string{"wait", "--for=condition=available", "--timeout=600s", "deployment/validator-controller-manager", "-n", "validator"}
ValidatorBasicAuthKeys = []string{"username", "password"}
ValidatorSinkKeys = map[vtypes.SinkType][]string{
Expand Down
8 changes: 3 additions & 5 deletions pkg/services/validator/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,27 +443,26 @@ func readIamPolicyRule(c *components.AWSPluginConfig, r *vpawsapi.IamPolicyRule,

func readIamPolicy() (vpawsapi.PolicyDocument, error) {
policyDoc := vpawsapi.PolicyDocument{}
inputType, err := prompts.Select("Add policy document via", []string{"Local Filepath", "File Editor"})
inputType, err := prompts.Select("Add policy document via", cfg.FileInputs)
if err != nil {
return policyDoc, err
}

for {
var policyBytes []byte
if inputType == "Local Filepath" {
if inputType == cfg.LocalFilepath {
policyFile, err := prompts.ReadFilePath("Policy Document Filepath", "", "Invalid policy document path", false)
if err != nil {
return policyDoc, err
}

policyBytes, err = os.ReadFile(policyFile) //#nosec
if err != nil {
return policyDoc, err
}
} else {
log.InfoCLI("Configure Policy Document")
time.Sleep(2 * time.Second)
policyFile, err := prompts.EditFileValidatedByFullContent(cfg.AWSPolicyDocumentPrompt, "", prompts.ValidateJson, 1)
policyFile, err := prompts.EditFileValidatedByFullContent(cfg.AWSPolicyDocumentPrompt, "", prompts.ValidateJSON, 1)
if err != nil {
return policyDoc, err
}
Expand All @@ -478,7 +477,6 @@ func readIamPolicy() (vpawsapi.PolicyDocument, error) {
if err != nil {
return policyDoc, err
}

if retry {
continue
}
Expand Down
10 changes: 4 additions & 6 deletions pkg/services/validator/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,27 +233,26 @@ func configureAzureRBACRulePermissionSets(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.")

inputType, err := prompts.Select("Add permission sets via", []string{"Local Filepath", "File Editor"})
inputType, err := prompts.Select("Add permission sets via", cfg.FileInputs)
if err != nil {
return err
}

for {
var permissionSetBytes []byte
if inputType == "Local Filepath" {
if inputType == cfg.LocalFilepath {
permissionSetFile, err := prompts.ReadFilePath("Permission sets file path", "", "Invalid file path", false)
if err != nil {
return err
}

permissionSetBytes, err = os.ReadFile(permissionSetFile) //#nosec
if err != nil {
return fmt.Errorf("failed to read permission sets file: %w", err)
}
} else {
log.InfoCLI("Configure permission sets")
time.Sleep(2 * time.Second)
permissionSetFile, err := prompts.EditFileValidatedByFullContent(cfg.AzurePermissionSetPrompt, "", prompts.ValidateJson, 1)
permissionSetFile, err := prompts.EditFileValidatedByFullContent(cfg.AzurePermissionSetPrompt, "", prompts.ValidateJSON, 1)
if err != nil {
return fmt.Errorf("failed to configure permission sets: %w", err)
}
Expand All @@ -266,9 +265,8 @@ func configureAzureRBACRulePermissionSets(r *plug.RBACRule) error {
log.ErrorCLI("Failed to unmarshal the provided permission sets", "err", errUnmarshal)
retry, err := prompts.ReadBool("Reconfigure permission sets", true)
if err != nil {
return fmt.Errorf("failed to prompt for reconfiguration of permission sets: %w", err)
return err
}

if retry {
continue
}
Expand Down
112 changes: 80 additions & 32 deletions pkg/services/validator/vmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package validator
import (
"context"
"fmt"
"os"
"reflect"
"slices"
"sort"
"strconv"
"strings"
"time"

"emperror.dev/errors"
"github.com/vmware/govmomi/object"
Expand Down Expand Up @@ -369,15 +371,10 @@ func readRolePrivilegeRule(c *components.VspherePluginConfig, r *components.Vsph
}

if reconfigurePrivileges {
privileges, err := LoadPrivileges(cfg.ValidatorVspherePrivilegeFile)
r.Privileges, err = readPrivileges(r.Privileges)
if err != nil {
return err
}
privileges, err = selectPrivileges(privileges)
if err != nil {
return err
}
r.Privileges = privileges
}

if idx == -1 {
Expand All @@ -391,41 +388,96 @@ func readRolePrivilegeRule(c *components.VspherePluginConfig, r *components.Vsph
return nil
}

// LoadPrivileges returns a slice of privilege IDs from the provided privilege file
func LoadPrivileges(privilegeFile string) ([]string, error) {
// loadPrivileges returns a slice of privilege IDs from the provided privilege file
func loadPrivileges(privilegeFile string) (string, func(string) error, error) {
privilegeBytes, err := embed.EFS.ReadFile(cfg.Validator, privilegeFile)
if err != nil {
return nil, err
return "", nil, err
}

var privilegeMap map[string][]string
if err := yaml.Unmarshal(privilegeBytes, &privilegeMap); err != nil {
return nil, err
return "", nil, err
}
privileges := privilegeMap["privilegeIds"]
return privileges, nil
slices.Sort(privileges)

validate := func(input string) error {
if !slices.Contains(privileges, strings.TrimSpace(input)) {
log.ErrorCLI("failed to read vCenter privileges", "invalidPrivilege", input)
return prompts.ErrValidationFailed
}
return nil
}

return strings.Join(privileges, "\n"), validate, nil
}

func selectPrivileges(allPrivileges []string) ([]string, error) {
var selectedPrivileges []string
slices.Sort(allPrivileges)
func readPrivileges(rulePrivileges []string) ([]string, error) {
defaultPrivileges, validate, err := loadPrivileges(cfg.ValidatorVspherePrivilegeFile)
if err != nil {
return nil, err
}
if len(rulePrivileges) > 0 {
defaultPrivileges = strings.Join(rulePrivileges, "\n")
}

log.InfoCLI(`
Configure vCenter privileges. Either provide a local file path to a
file containing vCenter privileges or edit the privileges directly.
log.InfoCLI("Select custom privileges:\n")
for {
privilege, err := prompts.Select("", allPrivileges)
if err != nil {
return nil, err
}
selectedPrivileges = append(selectedPrivileges, privilege)
If providing a local file path, the file should contain a list of
vCenter privileges, newline separated. Lines starting with '#' are
considered comments and are ignored.
add, err := prompts.ReadBool("Add another privilege", true)
if err != nil {
If editing the privileges directly, your default editor will be opened
with all valid vCenter privileges prepopulated for you to edit.
`)
inputType, err := prompts.Select("Add privileges via", cfg.FileInputs)
if err != nil {
return nil, err
}
if inputType == cfg.LocalFilepath {
return readPrivilegesFromFile(validate)
}

return readPrivilegesFromEditor(defaultPrivileges, validate)
}

func readPrivilegesFromEditor(defaultPrivileges string, validate func(string) error) ([]string, error) {
log.InfoCLI("Configure vCenter privileges")
time.Sleep(2 * time.Second)
joinedPrivileges, err := prompts.EditFileValidatedByLine(cfg.VcenterPrivilegePrompt, defaultPrivileges, "\n", validate, 1)
if err != nil {
return nil, err
}
privileges := strings.Split(joinedPrivileges, "\n")
return privileges, nil
}

func readPrivilegesFromFile(validate func(string) error) ([]string, error) {
privilegeFile, err := prompts.ReadFilePath("Privilege file path", "", "Invalid file path", false)
if err != nil {
return nil, err
}
privilegeBytes, err := os.ReadFile(privilegeFile) //#nosec
if err != nil {
return nil, fmt.Errorf("failed to read privilege file: %w", err)
}
privileges := strings.Split(string(privilegeBytes), "\n")
for _, p := range privileges {
if err := validate(p); err != nil {
retry, err := prompts.ReadBool("Reconfigure privileges", true)
if err != nil {
return nil, err
}
if retry {
return readPrivilegesFromFile(validate)
}
return nil, err
}
if !add {
break
}
}
return selectedPrivileges, nil
return privileges, nil
}

func configureEntityPrivilegeRules(ctx context.Context, c *components.VspherePluginConfig, driver vsphere.Driver, ruleNames *[]string, vSphereCloudDriver vsphere.Driver) error {
Expand Down Expand Up @@ -534,11 +586,7 @@ func readEntityPrivileges(ctx context.Context, c *components.VspherePluginConfig
if err != nil {
return err
}
privileges, err := LoadPrivileges(cfg.ValidatorVspherePrivilegeFile)
if err != nil {
return err
}
r.Privileges, err = selectPrivileges(privileges)
r.Privileges, err = readPrivileges(r.Privileges)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 039e520

Please sign in to comment.