-
Notifications
You must be signed in to change notification settings - Fork 170
ROX-12824: Add roxctl commands to generate Central DB bundle #3602
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
Changes from all commits
0631fe2
c393666
a9ba502
07f9eeb
73e7dd4
152f532
ce0026a
74fbcab
105c1ea
e401c06
6c75aa8
0ced447
04b9d69
be6ee33
0831fd4
990b118
2257c1f
dd2220a
774ac92
64dccec
88b938b
5e68048
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| package generate | ||
|
|
||
| import ( | ||
| "net/http" | ||
| "os" | ||
| "time" | ||
|
|
||
| "github.com/pkg/errors" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackrox/rox/pkg/buildinfo" | ||
| "github.com/stackrox/rox/pkg/errorhelpers" | ||
| "github.com/stackrox/rox/pkg/images/defaults" | ||
| "github.com/stackrox/rox/pkg/mtls" | ||
| "github.com/stackrox/rox/pkg/renderer" | ||
| "github.com/stackrox/rox/pkg/roxctl" | ||
| "github.com/stackrox/rox/pkg/set" | ||
| "github.com/stackrox/rox/pkg/zip" | ||
| "github.com/stackrox/rox/roxctl/common" | ||
| "github.com/stackrox/rox/roxctl/common/environment" | ||
| "github.com/stackrox/rox/roxctl/common/flags" | ||
| "github.com/stackrox/rox/roxctl/common/logger" | ||
| "github.com/stackrox/rox/roxctl/common/zipdownload" | ||
| ) | ||
|
|
||
| const ( | ||
| centralDBCertGeneratePath = "/api/extensions/certgen/centraldb" | ||
| ) | ||
|
|
||
| var ( | ||
| centralDBCertBundle = set.NewFrozenStringSet(mtls.CACertFileName, mtls.CentralDBCertFileName, mtls.CentralDBKeyFileName) | ||
| ) | ||
|
|
||
| type generateCommand struct { | ||
| // Properties that are bound to cobra flags. | ||
| config *renderer.Config | ||
|
|
||
| // Properties that are injected or constructed. | ||
| env environment.Environment | ||
|
|
||
| // timeout to make Central API call | ||
| timeout time.Duration | ||
| } | ||
|
|
||
| // Command represents the generate command. | ||
| func Command(cliEnvironment environment.Environment) *cobra.Command { | ||
| cmd := &generateCommand{config: &cfg, env: cliEnvironment} | ||
|
|
||
| c := &cobra.Command{ | ||
| Use: "generate", | ||
| Hidden: true, | ||
| } | ||
|
|
||
| if !buildinfo.ReleaseBuild { | ||
| flags.AddHelmChartDebugSetting(c) | ||
| } | ||
| c.PersistentFlags().BoolVar(&cmd.config.EnablePodSecurityPolicies, "enable-pod-security-policies", true, "Create PodSecurityPolicy resources (for pre-v1.25 Kubernetes)") | ||
| c.PersistentPreRunE = func(*cobra.Command, []string) error { | ||
| cmd.construct(c) | ||
| return cmd.populateMTLS() | ||
| } | ||
|
|
||
| c.AddCommand(k8s(cliEnvironment)) | ||
| c.AddCommand(openshift(cliEnvironment)) | ||
|
|
||
| return c | ||
| } | ||
|
|
||
| func (cmd *generateCommand) populateMTLS() error { | ||
| envLogger := cmd.env.Logger() | ||
| envLogger.InfofLn("Populating Central DB Certificate from bundle...") | ||
| fileMap, err := zipdownload.GetZipFiles(zipdownload.GetZipOptions{ | ||
| Path: centralDBCertGeneratePath, | ||
| Method: http.MethodPost, | ||
| Timeout: cmd.timeout, | ||
| BundleType: "central-db", | ||
| ExpandZip: true, | ||
| }, envLogger) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| err = verifyCentralDBBundleFiles(fileMap) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| cmd.config.SecretsByteMap = map[string][]byte{ | ||
| "ca.pem": fileMap[mtls.CACertFileName].Content, | ||
| "central-db-cert.pem": fileMap[mtls.CentralDBCertFileName].Content, | ||
| "central-db-key.pem": fileMap[mtls.CentralDBKeyFileName].Content, | ||
| "central-db-password": []byte(renderer.CreatePassword()), | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (cmd *generateCommand) construct(c *cobra.Command) { | ||
| cmd.timeout = flags.Timeout(c) | ||
| } | ||
|
|
||
| func generateBundleWrapper(config renderer.Config) (*zip.Wrapper, error) { | ||
| rendered, err := render(config) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| wrapper := zip.NewWrapper() | ||
| wrapper.AddFiles(rendered...) | ||
| return wrapper, errors.Wrap(err, "could not get scanner bundle") | ||
| } | ||
|
|
||
| func outputZip(envLogger logger.Logger, config renderer.Config) error { | ||
| envLogger.InfofLn("Generating Central DB bundle...") | ||
| common.LogInfoPsp(envLogger, config.EnablePodSecurityPolicies) | ||
| wrapper, err := generateBundleWrapper(config) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if roxctl.InMainImage() { | ||
| bytes, err := wrapper.Zip() | ||
| if err != nil { | ||
| return errors.Wrap(err, "error generating zip file") | ||
| } | ||
| _, err = os.Stdout.Write(bytes) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We made a huge effort to not use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dhaus67 Why it was not caught by forbidigo linter? 🤔
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the output of zipped files. I cannot use logger.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use |
||
| if err != nil { | ||
| return errors.Wrap(err, "couldn't write zip file") | ||
| } | ||
| return nil | ||
| } | ||
| outputPath, err := wrapper.Directory(config.OutputDir) | ||
| if err != nil { | ||
| return errors.Wrap(err, "error generating directory for Central output") | ||
| } | ||
| envLogger.InfofLn("Wrote central bundle to %q", outputPath) | ||
| return nil | ||
| } | ||
|
|
||
| func render(config renderer.Config) ([]*zip.File, error) { | ||
| flavor, err := defaults.GetImageFlavorByName(config.K8sConfig.ImageFlavorName, buildinfo.ReleaseBuild) | ||
| if err != nil { | ||
| return nil, common.ErrInvalidCommandOption.CausedByf("'--%s': %v", flags.ImageDefaultsFlagName, err) | ||
| } | ||
|
|
||
| return renderer.RenderCentralDBOnly(config, flavor) | ||
| } | ||
|
|
||
| func verifyCentralDBBundleFiles(fm map[string]*zip.File) error { | ||
| var errs errorhelpers.ErrorList | ||
|
|
||
| checkList := centralDBCertBundle.Unfreeze() | ||
| for k, v := range fm { | ||
| if len(v.Content) == 0 { | ||
| errs.AddError(errors.Errorf("empty file in Central DB certificate bundle: %s", v.Name)) | ||
| } | ||
| if !centralDBCertBundle.Contains(k) { | ||
| errs.AddError(errors.Errorf("unexpected file in Central DB certificate bundle: %s", k)) | ||
| } | ||
| checkList.Remove(k) | ||
| } | ||
| if checkList.Cardinality() != 0 { | ||
| errs.AddError(errors.Errorf("missing file(s) in Central DB certificate bundle %s", checkList.ElementsString(","))) | ||
| } | ||
| return errs.ToError() | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| package generate | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/pkg/errors" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackrox/rox/generated/storage" | ||
| "github.com/stackrox/rox/pkg/errox" | ||
| "github.com/stackrox/rox/pkg/renderer" | ||
| "github.com/stackrox/rox/pkg/roxctl" | ||
| "github.com/stackrox/rox/roxctl/common" | ||
| "github.com/stackrox/rox/roxctl/common/environment" | ||
| "github.com/stackrox/rox/roxctl/common/flags" | ||
| "github.com/stackrox/rox/roxctl/common/util" | ||
| ) | ||
|
|
||
| const ( | ||
| defaultCentralDBBundle = "central-db-bundle" | ||
| ) | ||
|
|
||
| func orchestratorCommand(shortName, longName string) *cobra.Command { | ||
| c := &cobra.Command{ | ||
| Use: shortName, | ||
| Short: shortName, | ||
| Long: longName, | ||
| RunE: util.RunENoArgs(func(*cobra.Command) error { | ||
| return errox.InvalidArgs.New("storage type must be specified") | ||
| }), | ||
| } | ||
| if !roxctl.InMainImage() { | ||
| c.PersistentFlags().Var(common.NewOutputDir(&cfg.OutputDir, defaultCentralDBBundle), "output-dir", "the directory to output the deployment bundle to") | ||
| } | ||
| return c | ||
| } | ||
|
|
||
| func k8sBasedOrchestrator(cliEnvironment environment.Environment, k8sConfig *renderer.K8sConfig, shortName, longName string, getClusterType func() (storage.ClusterType, error)) *cobra.Command { | ||
| c := orchestratorCommand(shortName, longName) | ||
| c.PersistentPreRunE = func(*cobra.Command, []string) error { | ||
| clusterType, err := getClusterType() | ||
| if err != nil { | ||
| return errors.Wrap(err, "determining cluster type") | ||
| } | ||
| cfg.K8sConfig = k8sConfig | ||
| cfg.ClusterType = clusterType | ||
| return nil | ||
| } | ||
|
|
||
| c.AddCommand(externalVolume(cliEnvironment)) | ||
| c.AddCommand(hostPathVolume(cliEnvironment)) | ||
| c.AddCommand(noVolume(cliEnvironment)) | ||
|
|
||
| // Adds k8s specific flags | ||
| flags.AddImageDefaults(c.PersistentFlags(), &k8sConfig.ImageFlavorName) | ||
|
|
||
| defaultImageHelp := fmt.Sprintf("(if unset, a default will be used according to --%s)", flags.ImageDefaultsFlagName) | ||
| c.PersistentFlags().StringVarP(&k8sConfig.CentralDBImage, flags.FlagNameCentralDBImage, "", "", "central-db image to use"+defaultImageHelp) | ||
| k8sConfig.EnableCentralDB = true | ||
|
|
||
| return c | ||
| } | ||
|
|
||
| func newK8sConfig() *renderer.K8sConfig { | ||
| return &renderer.K8sConfig{} | ||
| } | ||
|
|
||
| func k8s(cliEnvironment environment.Environment) *cobra.Command { | ||
| k8sConfig := newK8sConfig() | ||
| return k8sBasedOrchestrator(cliEnvironment, k8sConfig, "k8s", "Kubernetes", func() (storage.ClusterType, error) { return storage.ClusterType_KUBERNETES_CLUSTER, nil }) | ||
| } | ||
|
|
||
| func openshift(cliEnvironment environment.Environment) *cobra.Command { | ||
| k8sConfig := newK8sConfig() | ||
|
|
||
| var openshiftVersion int | ||
| c := k8sBasedOrchestrator(cliEnvironment, k8sConfig, "openshift", "Openshift", func() (storage.ClusterType, error) { | ||
| switch openshiftVersion { | ||
| case 3: | ||
| return storage.ClusterType_OPENSHIFT_CLUSTER, nil | ||
| case 4: | ||
| return storage.ClusterType_OPENSHIFT4_CLUSTER, nil | ||
| default: | ||
| return 0, errors.Errorf("invalid OpenShift version %d, supported values are '3' and '4'", openshiftVersion) | ||
| } | ||
| }) | ||
|
|
||
| c.PersistentFlags().IntVar(&openshiftVersion, "openshift-version", 3, "the OpenShift major version (3 or 4) to deploy on") | ||
| return c | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package generate | ||
|
|
||
| import ( | ||
| "github.com/stackrox/rox/pkg/errox" | ||
| "github.com/stackrox/rox/pkg/renderer" | ||
| ) | ||
|
|
||
| var ( | ||
| cfg renderer.Config | ||
| ) | ||
|
|
||
| func validateConfig(c *renderer.Config) error { | ||
| if c.HostPath == nil { | ||
| return nil | ||
| } | ||
| return validateHostPathInstance(c.HostPath.DB) | ||
| } | ||
|
|
||
| func validateHostPathInstance(instance *renderer.HostPathPersistenceInstance) error { | ||
| if instance == nil { | ||
| return nil | ||
| } | ||
| if instance.HostPath == "" { | ||
| return errox.InvalidArgs.New("non-empty HostPath must be specified") | ||
| } | ||
| if (instance.NodeSelectorKey == "") != (instance.NodeSelectorValue == "") { | ||
| return errox.InvalidArgs.New("both node selector key and node selector value must be specified when using a hostpath") | ||
| } | ||
| return nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package generate | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/stackrox/rox/pkg/renderer" | ||
| "github.com/stackrox/rox/roxctl/common/environment" | ||
| ) | ||
|
|
||
| func volumeCommand(name string) *cobra.Command { | ||
| return &cobra.Command{ | ||
| Use: name, | ||
| Short: fmt.Sprintf("adds a %s", name), | ||
| Long: fmt.Sprintf(`adds a %s external volume to Central DB`, name), | ||
| } | ||
| } | ||
|
|
||
| func externalVolume(cliEnvironment environment.Environment) *cobra.Command { | ||
| external := &renderer.ExternalPersistence{ | ||
| DB: &renderer.ExternalPersistenceInstance{}, | ||
| } | ||
| c := volumeCommand("pvc") | ||
| c.RunE = func(c *cobra.Command, args []string) error { | ||
| cfg.External = external | ||
| if err := validateConfig(&cfg); err != nil { | ||
| return err | ||
| } | ||
| return outputZip(cliEnvironment.Logger(), cfg) | ||
| } | ||
| c.Flags().StringVarP(&external.DB.Name, "name", "", "central-db", "external volume name for Central DB") | ||
| c.Flags().StringVarP(&external.DB.StorageClass, "storage-class", "", "", "storage class name for Central DB (optional if you have a default StorageClass configured)") | ||
| c.Flags().Uint32VarP(&external.DB.Size, "size", "", 100, "external volume size in Gi for Central DB") | ||
dhaus67 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return c | ||
| } | ||
|
|
||
| func noVolume(cliEnvironment environment.Environment) *cobra.Command { | ||
dhaus67 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| c := volumeCommand("none") | ||
| c.RunE = func(c *cobra.Command, args []string) error { | ||
| if err := validateConfig(&cfg); err != nil { | ||
| return err | ||
| } | ||
| return outputZip(cliEnvironment.Logger(), cfg) | ||
| } | ||
| c.Hidden = true | ||
| return c | ||
| } | ||
|
|
||
| func hostPathVolume(cliEnvironment environment.Environment) *cobra.Command { | ||
| hostpath := &renderer.HostPathPersistence{ | ||
| DB: &renderer.HostPathPersistenceInstance{}, | ||
| } | ||
| c := volumeCommand("hostpath") | ||
| c.RunE = func(c *cobra.Command, args []string) error { | ||
| cfg.HostPath = hostpath | ||
| if err := validateConfig(&cfg); err != nil { | ||
| return err | ||
| } | ||
| return outputZip(cliEnvironment.Logger(), cfg) | ||
| } | ||
| c.Flags().StringVarP(&hostpath.DB.HostPath, "hostpath", "", "/var/lib/stackrox-central", "path on the host") | ||
| c.Flags().StringVarP(&hostpath.DB.NodeSelectorKey, "node-selector-key", "", "", "node selector key (e.g. kubernetes.io/hostname)") | ||
| c.Flags().StringVarP(&hostpath.DB.NodeSelectorValue, "node-selector-value", "", "", "node selector value") | ||
|
|
||
| return c | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.