├── .github └── workflows │ ├── add-issue-to-triage.yml │ └── add-new-pr-to-oss-triaging.yml ├── .gitignore ├── .gitmodules ├── Readme.md ├── acs-export-example ├── .gitignore ├── cmd │ └── root.go ├── demo.tape ├── go.mod ├── go.sum ├── main.go ├── pkg │ ├── config │ │ └── config.go │ ├── csv │ │ └── csv.go │ ├── export │ │ └── export.go │ ├── filter │ │ └── filter.go │ └── table │ │ └── table.go └── readme.md ├── api-examples ├── README.md ├── authprovider-minrole.md ├── delete_group.md ├── groupsbatch_newgroups.md ├── groupservice_creategroup.md └── simple-alerts.md ├── backups ├── api-key-secret.yaml ├── cron-backup.yaml ├── cron-clean-backup.yaml ├── readme.md └── retrieve-backups-pod.yaml ├── ci ├── Azure DevOps │ └── Pipelines │ │ ├── README.md │ │ └── azure-pipelines.yml ├── CircleCI │ ├── .circleci │ │ └── config.yml │ ├── README.md │ └── deploy.yml ├── GitHub │ ├── kube-linter │ │ ├── .github │ │ │ ├── actions │ │ │ │ └── kube-lint │ │ │ │ │ └── action.yml │ │ │ └── workflows │ │ │ │ └── kube-linter.yml │ │ ├── .kube-linter │ │ │ └── config.yaml │ │ ├── README.md │ │ └── yamls │ │ │ └── deploy.yaml │ └── stackrox-action │ │ ├── .github │ │ ├── actions │ │ │ ├── stackrox-check │ │ │ │ └── action.yml │ │ │ └── stackrox-scan │ │ │ │ └── action.yml │ │ └── workflows │ │ │ └── main.yml │ │ └── README.md ├── GitLab │ ├── .gitlab-ci.yml │ └── README.md ├── Tekton │ ├── README.md │ ├── Sample │ │ ├── rox-pipeline.yml │ │ └── rox-secrets.yml │ └── Tasks │ │ ├── rox-deployment-check-task.yml │ │ ├── rox-image-check-task.yml │ │ └── rox-image-scan-task.yml ├── argo │ ├── README.md │ └── argo.yml └── function │ └── Google Function │ ├── CI integration sample │ └── .circleci │ │ └── config.yml │ ├── README.md │ └── roxctl_image_check │ ├── main.py │ └── requirements.txt ├── completions └── fish │ └── roxctl.fish ├── compliance └── scan-compliance.sh ├── guides └── cloud-provider-integrations │ └── azure-service-principal-m2m-auth.md ├── ingress ├── contour │ └── central-ingress.yaml ├── haproxy │ ├── Readme.md │ ├── central-hap-ingress.yaml │ └── haproxy-controller.yaml ├── istio-gw │ ├── Readme.md │ └── central-istio-gw-passthrough.yaml ├── nginx │ ├── Readme.md │ ├── central-nginx-encrypt-ingress.yaml │ └── central-nginx-passthrough-ingress.yaml └── traefik │ ├── Readme.md │ └── central-traefik-ingress.yaml ├── policies ├── CVE-2021-4034-build-deploy.json ├── CVE-2021-44228-build-deploy.json ├── leaky-vessels.json ├── oc-debug-runtime.json ├── polkit-execution.json └── polkit-in-image.json ├── terraform └── azure-sentinel │ ├── README.md │ ├── main.tf │ ├── provider.tf │ └── variables.tf ├── util-scripts ├── acs-correlation-example │ ├── Dockerfile │ ├── README.md │ ├── acs_request.py │ ├── app.py │ ├── config.py │ ├── endpoint_list.json │ ├── logging.conf │ ├── output │ │ ├── sample_cluster_namespace_deployment_alert_output_file.json │ │ └── sample_endpoint_policy_alert_count_output_file.json │ └── requirements.txt ├── compliance-scans-classifications │ ├── stackrox_classifications.sh │ └── stackrox_compliance_scan.sh ├── component-details-to-csv │ ├── README.md │ └── component_details_csv.sh ├── cronjob-upload-vuln-definitions │ ├── fetchvulns-cronjob.yaml │ └── upload-vulns-configmap.yaml ├── export-all-policies │ ├── README.md │ └── export-all-policies.sh ├── export-cves-to-csv │ ├── README.md │ └── create-csv.sh ├── external-entities │ ├── README.md │ ├── external-entities.py │ └── requirements.txt ├── generate_violations_csv │ ├── README.md │ ├── generate_violations_csv.py │ └── requirements.txt ├── health-check │ ├── README.md │ └── health-check.sh ├── image-cve-report │ ├── README.md │ └── image-cve-report.sh ├── listening-endpoints │ ├── README.md │ └── listening-endpoints.sh ├── log4shell │ ├── README.md │ ├── log4shell-check.py │ └── requirements.txt ├── policy-copy-all │ ├── README.md │ └── policy-copy-all.sh ├── policy-update │ ├── README.md │ └── policy-update.sh ├── policy-utils │ └── policies-csv │ │ ├── README.md │ │ └── policies-csv.sh ├── rhcos-node-cves │ ├── README.md │ └── node-cve-report.sh ├── roxctl-base-image │ ├── README.md │ └── base.py ├── roxctl-grace-period │ ├── README.md │ └── grace.py ├── scan-all-registry-images │ ├── README.md │ └── ecr-scan-roxctl.sh ├── violations-to-csv │ ├── README.md │ └── violations-to-csv.sh └── vuln-violation-details │ └── vulnvdetails.sh └── vulnerability-management └── export-workloads ├── README.md ├── export-workloads.py └── export-workloads.sh /.github/workflows/add-issue-to-triage.yml: -------------------------------------------------------------------------------- 1 | name: Add any new issue to OSS Triaging project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | add-to-project: 10 | name: Add issue to project 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/add-to-project@v0.3.0 14 | with: 15 | project-url: https://github.com/orgs/stackrox/projects/2 16 | github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/add-new-pr-to-oss-triaging.yml: -------------------------------------------------------------------------------- 1 | name: Add any new Pull Request to OSS Triaging project 2 | on: 3 | pull_request_target: 4 | types: [opened, reopened] 5 | 6 | env: 7 | EXTERNAL_PR_LABEL: external-contributor 8 | PROJECT_URL: https://github.com/orgs/stackrox/projects/2 # OSS Triaging board 9 | 10 | jobs: 11 | check-pr-if-external: 12 | name: Add external label to pull request if outside StackRox 13 | runs-on: ubuntu-latest 14 | env: 15 | GH_TOKEN: ${{ github.token }} 16 | BASE_REPO: ${{ github.repository }} 17 | HEAD_REPO: ${{ github.event.pull_request.head.user.login }}/${{ github.event.pull_request.head.repo.name }} 18 | outputs: 19 | is_external_pr: ${{ steps.check-external-pr.outputs.is_external_pr }} 20 | steps: 21 | - name: Check out code 22 | uses: actions/checkout@v3 23 | - id: check-external-pr 24 | run: | 25 | set -uo pipefail 26 | if [[ $BASE_REPO != $HEAD_REPO ]]; then 27 | echo "::set-output name=is_external_pr::true" 28 | gh pr edit \ 29 | ${{ github.event.pull_request.number }} \ 30 | --add-label ${EXTERNAL_PR_LABEL} 31 | else 32 | echo "::set-output name=is_external_pr::false" 33 | fi 34 | 35 | add-to-project: 36 | name: Add pull request to project 37 | runs-on: ubuntu-latest 38 | needs: [check-pr-if-external] 39 | if: needs.check-pr-if-external.outputs.is_external_pr == 'true' 40 | steps: 41 | - uses: actions/add-to-project@v0.3.0 42 | with: 43 | project-url: ${{ env.PROJECT_URL }} 44 | github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://gist.github.com/octocat/9257657 2 | 3 | .idea 4 | 5 | # Compiled source # 6 | ################### 7 | *.com 8 | *.class 9 | *.dll 10 | *.exe 11 | *.o 12 | *.so 13 | 14 | # Packages # 15 | ############ 16 | # it's better to unpack these files and commit the raw source 17 | # git has its own built in compression methods 18 | *.7z 19 | *.dmg 20 | *.gz 21 | *.iso 22 | *.jar 23 | *.rar 24 | *.tar 25 | *.zip 26 | 27 | # Logs and databases # 28 | ###################### 29 | *.log 30 | *.sql 31 | *.sqlite 32 | 33 | # OS generated files # 34 | ###################### 35 | .DS_Store 36 | .DS_Store? 37 | ._* 38 | .Spotlight-V100 39 | .Trashes 40 | ehthumbs.db 41 | Thumbs.db 42 | 43 | # Deployment # 44 | ############## 45 | sensor*.zip 46 | tmp 47 | /central-bundle 48 | /**/.terraform 49 | /terraform/**/.terraform.lock.hcl 50 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackrox/contributions/b4cb0161e934907da97f96540e26cc85a89f385c/.gitmodules -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # StackRox community contributions 2 | 3 | Welcome to the StackRox contributions repository. This repository stores a variety of configuration files, scripts, and samples related to the deployment and use of the [StackRox Kubernetes security platform](https://www.stackrox.io) and [Red Hat Advanced Cluster Security](https://www.redhat.com/en/technologies/cloud-computing/openshift/advanced-cluster-security-kubernetes). 4 | 5 | All code in this repo is provided as-is without warranty or support from Red Hat. 6 | 7 | Join the #stackrox channel on [CNCF Slack](https://cncf.slack.com/) for community discussion and support. 8 | 9 | We'd love your feedback! Please take care with any of the configurations in this repo before applying using these in your environment. 10 | 11 | ## What's in this Repository 12 | * `ci/` samples for using StackRox *roxctl* command-line tool in CI/CD pipelines 13 | * `ingress/` configurations for StackRox under popular Kubernetes ingress controllers 14 | * `completions/` shell auto-completions for roxctl 15 | * `util-scripts/` scripts using Stackrox API or roxctl for popular tasks (export to csv,...) 16 | * `guides/` instructions on how to configure ACS or integrate it with 3rd party services 17 | -------------------------------------------------------------------------------- /acs-export-example/.gitignore: -------------------------------------------------------------------------------- 1 | acs-export-example 2 | *.csv 3 | -------------------------------------------------------------------------------- /acs-export-example/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/spf13/cobra" 11 | 12 | "github.com/stackrox/contributions/acs-export-example/pkg/config" 13 | "github.com/stackrox/contributions/acs-export-example/pkg/csv" 14 | "github.com/stackrox/contributions/acs-export-example/pkg/export" 15 | "github.com/stackrox/contributions/acs-export-example/pkg/filter" 16 | "github.com/stackrox/contributions/acs-export-example/pkg/table" 17 | ) 18 | 19 | var cfg = config.ConfigType{} 20 | var stats = &config.Stats{} 21 | 22 | var rootCmd = &cobra.Command{ 23 | Use: "acs-export-example", 24 | Short: "Use the ACS export APIs", 25 | Long: `CLI to browse data pulled from ACS (Advanced Cluster Security) (i.e. StackRox).`, 26 | Run: func(cmd *cobra.Command, args []string) { 27 | if err := validateFlags(); err != nil { 28 | fmt.Printf("%v\n", err) 29 | os.Exit(1) 30 | } 31 | 32 | ctx := context.Background() 33 | 34 | exporter, err := export.New(ctx, stats) 35 | if err != nil { 36 | panic(errors.Wrap(err, "could not create exporter")) 37 | } 38 | 39 | query := cfg.QueryFilter 40 | 41 | if cfg.FilterType == "server" { 42 | query = filter.BuildServerQuery(cfg) 43 | } 44 | 45 | os.Stderr.WriteString("Fetching deployments\n") 46 | deployments, err := exporter.GetDeployments(query) 47 | if err != nil { 48 | panic(errors.Wrap(err, "could not get deployments")) 49 | } 50 | 51 | os.Stderr.WriteString("Fetching images\n") 52 | images, err := exporter.GetImages(query) 53 | if err != nil { 54 | panic(errors.Wrap(err, "could not get images")) 55 | } 56 | 57 | if cfg.FilterType == "client" { 58 | deployments, images = filter.ClientFilter(deployments, images, cfg, stats) 59 | } 60 | 61 | // This runs for both client and server filtering because the server 62 | // doesn't filter out CVEs off of image scans that don't match the CVE filter 63 | deployments, images = filter.ClientVulnFilter(deployments, images, cfg, stats) 64 | 65 | if cfg.Output == "table" { 66 | if err = table.RenderTable(deployments, images); err != nil { 67 | panic(errors.Wrap(err, "Failed to render table")) 68 | } 69 | } else if cfg.Output == "csv" { 70 | if err = csv.RenderCsv(deployments, images); err != nil { 71 | panic(errors.Wrap(err, "Failed to render table")) 72 | } 73 | } 74 | 75 | if cfg.Stats { 76 | os.Stderr.WriteString(stats.String()) 77 | } 78 | }, 79 | } 80 | 81 | func Execute() { 82 | if err := rootCmd.Execute(); err != nil { 83 | fmt.Println(err) 84 | os.Exit(1) 85 | } 86 | } 87 | 88 | func contains(a []string, elem string) bool { 89 | for _, i := range a { 90 | if i == elem { 91 | return true 92 | } 93 | } 94 | 95 | return false 96 | } 97 | 98 | func quoteWrap(a []string) string { 99 | var buffer bytes.Buffer 100 | 101 | buffer.WriteString("[") 102 | for i, s := range a { 103 | buffer.WriteString(fmt.Sprintf("\"%s\"", s)) 104 | 105 | if i < len(a)-1 { 106 | buffer.WriteString(", ") 107 | } 108 | } 109 | buffer.WriteString("]") 110 | 111 | return buffer.String() 112 | } 113 | 114 | func validateFlags() error { 115 | outputOptions := []string{"table", "csv"} 116 | filterTypeOptions := []string{"client", "server"} 117 | fixableOptions := []string{"true", "false", ""} 118 | 119 | if !contains(outputOptions, cfg.Output) { 120 | return errors.Errorf("Invalid value for --output=\"%s\". Available options: %v", cfg.Output, quoteWrap(outputOptions)) 121 | } 122 | 123 | if !contains(filterTypeOptions, cfg.FilterType) { 124 | return errors.Errorf("Invalid value for --filter-type=\"%s\". Available options: %v", cfg.FilterType, quoteWrap(filterTypeOptions)) 125 | } 126 | 127 | if !contains(fixableOptions, cfg.FixableFilter) { 128 | return errors.Errorf("Invalid value for --fixable=\"%s\". Available options: %v", cfg.FixableFilter, quoteWrap(fixableOptions)) 129 | } 130 | 131 | if cfg.QueryFilter != "" && cfg.FilterType == "server" { 132 | return errors.New("Cannot supply a query filter when --filter-type=server") 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func init() { 139 | rootCmd.PersistentFlags().StringVarP(&cfg.Output, "output", "o", "table", "Output format. Available options: [table, csv]") 140 | rootCmd.PersistentFlags().StringVarP(&cfg.NamespaceFilter, "namespace", "n", "", "Namespace client-side filter.") 141 | rootCmd.PersistentFlags().StringVarP(&cfg.ClusterFilter, "cluster", "c", "", "Cluster client-side filter.") 142 | rootCmd.PersistentFlags().StringVarP(&cfg.ImageNameFilter, "image", "i", "", "Image name client-side filter.") 143 | rootCmd.PersistentFlags().StringVarP(&cfg.VulnerabilityFilter, "vuln", "v", "", "Vulnerability client-side filter.") 144 | rootCmd.PersistentFlags().StringVarP(&cfg.QueryFilter, "query", "q", "", "Pass a query string to the server. Incompatible with --filter-type=server") 145 | rootCmd.PersistentFlags().StringVarP(&cfg.FixableFilter, "fixable", "f", "", "Filter on whether a cve is fixable. Available options: [true, false, \"\"].") 146 | rootCmd.PersistentFlags().StringVarP(&cfg.FilterType, "filter-type", "t", "client", "Where to do the param-based filtering. Available options: [client, server]") 147 | rootCmd.PersistentFlags().BoolVarP(&cfg.Stats, "stats", "s", false, "Print stats about the export") 148 | } 149 | -------------------------------------------------------------------------------- /acs-export-example/demo.tape: -------------------------------------------------------------------------------- 1 | Output demo.gif 2 | 3 | Set FontSize 12 4 | Set Width 1600 5 | Set Height 700 6 | 7 | Type "./acs-export-example | head -n 38" 8 | Sleep 500ms 9 | Enter 10 | Sleep 15s 11 | -------------------------------------------------------------------------------- /acs-export-example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/stackrox/contributions/acs-export-example/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /acs-export-example/pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | type ConfigType struct { 10 | Output string 11 | ClusterFilter string 12 | ImageNameFilter string 13 | NamespaceFilter string 14 | QueryFilter string 15 | VulnerabilityFilter string 16 | FixableFilter string 17 | FilterType string 18 | Stats bool 19 | } 20 | 21 | func (cfg *ConfigType) QueryStrings() map[string]string { 22 | ret := map[string]string{} 23 | ret["CLUSTER"] = "r/" + cfg.ClusterFilter 24 | ret["IMAGE"] = "r/" + cfg.ImageNameFilter 25 | ret["NAMESPACE"] = cfg.NamespaceFilter 26 | ret["CVE"] = "r/" + cfg.VulnerabilityFilter 27 | ret["FIXABLE"] = cfg.FixableFilter 28 | return ret 29 | } 30 | 31 | type Stats struct { 32 | ConnectDuration time.Duration 33 | DeploymentExportDuration time.Duration 34 | ImageExportDuration time.Duration 35 | DeploymentFilterDuration time.Duration 36 | ImageFilterDuration time.Duration 37 | DeploymentExportCount int 38 | ImageExportCount int 39 | FilteredDeploymentExportCount int 40 | FilteredImageExportCount int 41 | } 42 | 43 | func (s *Stats) String() string { 44 | var buffer bytes.Buffer 45 | 46 | buffer.WriteString("Durations:\n") 47 | buffer.WriteString(fmt.Sprintf(" Connect: %v\n", s.ConnectDuration)) 48 | buffer.WriteString(fmt.Sprintf(" Deployment Export: %v \n", s.DeploymentExportDuration)) 49 | buffer.WriteString(fmt.Sprintf(" Image Export: %v \n", s.ImageExportDuration)) 50 | if s.DeploymentFilterDuration > 0 { 51 | buffer.WriteString(fmt.Sprintf(" Deployment Filtering: %v\n", s.DeploymentFilterDuration)) 52 | } 53 | buffer.WriteString(fmt.Sprintf(" Image Filtering: %v \n", s.ImageFilterDuration)) 54 | buffer.WriteString("\nCounts:\n") 55 | buffer.WriteString(fmt.Sprintf(" Deployments: %v \n", s.DeploymentExportCount)) 56 | buffer.WriteString(fmt.Sprintf(" Images: %v \n", s.ImageExportCount)) 57 | if s.DeploymentFilterDuration > 0 { 58 | buffer.WriteString(fmt.Sprintf(" Filtered Deployments: %v\n", s.FilteredDeploymentExportCount)) 59 | } 60 | buffer.WriteString(fmt.Sprintf(" Filtered Images: %v \n", s.FilteredImageExportCount)) 61 | 62 | return buffer.String() 63 | } 64 | -------------------------------------------------------------------------------- /acs-export-example/pkg/csv/csv.go: -------------------------------------------------------------------------------- 1 | package csv 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "os" 7 | 8 | storage "github.com/stackrox/rox/generated/storage" 9 | ) 10 | 11 | func RenderCsv(deployments []*storage.Deployment, images []*storage.Image) error { 12 | imageMap := map[string]*storage.Image{} 13 | 14 | for _, image := range images { 15 | imageMap[image.Name.FullName] = image 16 | } 17 | 18 | writer := csv.NewWriter(os.Stdout) 19 | defer writer.Flush() 20 | 21 | headers := []string{"CVE", "Severity", "CVSS", "Status", "Component", "Fixed In", "Image", "Deployment", "Namespace", "Cluster"} 22 | writer.Write(headers) 23 | 24 | for _, d := range deployments { 25 | for _, container := range d.Containers { 26 | imageName := container.Image.Name.FullName 27 | 28 | image, found := imageMap[imageName] 29 | if !found || image.Scan == nil { 30 | continue 31 | } 32 | 33 | for _, component := range image.Scan.Components { 34 | for _, vuln := range component.Vulns { 35 | score := "" 36 | if vuln.CvssV3 != nil { 37 | score = fmt.Sprintf("v3: %.2f", vuln.CvssV3.Score) 38 | } else if vuln.CvssV2 != nil { 39 | score = fmt.Sprintf("v2: %.2f", vuln.CvssV2.Score) 40 | } 41 | 42 | status := "" 43 | if vuln.GetFixedBy() != "" { 44 | status = "fixable" 45 | } 46 | 47 | row := []string{vuln.Cve, vuln.Severity.String(), score, status, component.Name, vuln.GetFixedBy(), imageName, d.Name, d.Namespace, d.ClusterName} 48 | writer.Write(row) 49 | } 50 | } 51 | } 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /acs-export-example/pkg/export/export.go: -------------------------------------------------------------------------------- 1 | package export 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "time" 7 | 8 | "github.com/pkg/errors" 9 | "google.golang.org/grpc" 10 | 11 | v1 "github.com/stackrox/rox/generated/api/v1" 12 | storage "github.com/stackrox/rox/generated/storage" 13 | "github.com/stackrox/rox/roxctl/common" 14 | "github.com/stackrox/rox/roxctl/common/auth" 15 | roxctlIO "github.com/stackrox/rox/roxctl/common/io" 16 | "github.com/stackrox/rox/roxctl/common/logger" 17 | "github.com/stackrox/rox/roxctl/common/printer" 18 | 19 | "github.com/stackrox/contributions/acs-export-example/pkg/config" 20 | ) 21 | 22 | type Exporter struct { 23 | ctx context.Context 24 | conn *grpc.ClientConn 25 | stats *config.Stats 26 | } 27 | 28 | func New(ctx context.Context, stats *config.Stats) (Exporter, error) { 29 | defaultIO := roxctlIO.DefaultIO() 30 | start := time.Now() 31 | conn, err := common.GetGRPCConnection(auth.TokenAuth(), logger.NewLogger(defaultIO, printer.DefaultColorPrinter())) 32 | if err != nil { 33 | return Exporter{}, errors.Wrap(err, "could not establish gRPC connection to central") 34 | } 35 | 36 | stats.ConnectDuration = time.Now().Sub(start) 37 | 38 | return Exporter{ 39 | ctx: ctx, 40 | conn: conn, 41 | stats: stats, 42 | }, nil 43 | } 44 | 45 | func (ex *Exporter) GetImages(query string) ([]*storage.Image, error) { 46 | svc := v1.NewImageServiceClient(ex.conn) 47 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) 48 | defer cancel() 49 | 50 | start := time.Now() 51 | client, err := svc.ExportImages(ctx, &v1.ExportImageRequest{Query: query}) 52 | if err != nil { 53 | return nil, errors.Wrap(err, "could not initialize stream client") 54 | } 55 | 56 | images := []*storage.Image{} 57 | for { 58 | image, err := client.Recv() 59 | if err != nil { 60 | if errors.Is(err, io.EOF) { 61 | break 62 | } 63 | return nil, errors.Wrap(err, "stream broken by unexpected error") 64 | } 65 | 66 | images = append(images, image.Image) 67 | } 68 | 69 | ex.stats.ImageExportDuration = time.Now().Sub(start) 70 | ex.stats.ImageExportCount = len(images) 71 | 72 | return images, nil 73 | } 74 | 75 | func (ex *Exporter) GetDeployments(query string) ([]*storage.Deployment, error) { 76 | svc := v1.NewDeploymentServiceClient(ex.conn) 77 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) 78 | defer cancel() 79 | 80 | start := time.Now() 81 | client, err := svc.ExportDeployments(ctx, &v1.ExportDeploymentRequest{Query: query}) 82 | if err != nil { 83 | return nil, errors.Wrap(err, "could not initialize stream client") 84 | } 85 | 86 | deployments := []*storage.Deployment{} 87 | for { 88 | deployment, err := client.Recv() 89 | if err != nil { 90 | if errors.Is(err, io.EOF) { 91 | break 92 | } 93 | return nil, errors.Wrap(err, "stream broken by unexpected error") 94 | } 95 | 96 | deployments = append(deployments, deployment.Deployment) 97 | } 98 | 99 | ex.stats.DeploymentExportDuration = time.Now().Sub(start) 100 | ex.stats.DeploymentExportCount = len(deployments) 101 | 102 | return deployments, nil 103 | } 104 | -------------------------------------------------------------------------------- /acs-export-example/pkg/filter/filter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/stackrox/contributions/acs-export-example/pkg/config" 11 | storage "github.com/stackrox/rox/generated/storage" 12 | ) 13 | 14 | func keepVulnBasedOnFixableFilter(vuln *storage.EmbeddedVulnerability, fixableFilter string) bool { 15 | if fixableFilter == "" { 16 | return true 17 | } 18 | 19 | fixable := "true" 20 | 21 | if vuln.GetFixedBy() == "" { 22 | fixable = "false" 23 | } 24 | 25 | return fixableFilter == fixable 26 | } 27 | 28 | func ClientVulnFilter(deployments []*storage.Deployment, images []*storage.Image, cfg config.ConfigType, stats *config.Stats) (filteredDeployments []*storage.Deployment, filteredImages []*storage.Image) { 29 | start := time.Now() 30 | for _, image := range images { 31 | vulnFound := false 32 | if image.Scan != nil { 33 | for _, component := range image.Scan.Components { 34 | vulnsToKeep := []*storage.EmbeddedVulnerability{} 35 | for _, vuln := range component.Vulns { 36 | if strings.Contains(vuln.Cve, cfg.VulnerabilityFilter) && keepVulnBasedOnFixableFilter(vuln, cfg.FixableFilter) { 37 | vulnFound = true 38 | vulnsToKeep = append(vulnsToKeep, vuln) 39 | } 40 | } 41 | component.Vulns = vulnsToKeep 42 | } 43 | } 44 | 45 | if vulnFound { 46 | filteredImages = append(filteredImages, image) 47 | } 48 | } 49 | 50 | stats.ImageFilterDuration = stats.ImageFilterDuration + (time.Now().Sub(start)) 51 | stats.FilteredImageExportCount = len(filteredImages) 52 | 53 | filteredDeployments = deployments 54 | return 55 | } 56 | 57 | func ClientFilter(deployments []*storage.Deployment, images []*storage.Image, cfg config.ConfigType, stats *config.Stats) (filteredDeployments []*storage.Deployment, filteredImages []*storage.Image) { 58 | 59 | start := time.Now() 60 | for _, deployment := range deployments { 61 | if !strings.Contains(deployment.Namespace, cfg.NamespaceFilter) { 62 | continue 63 | } 64 | 65 | if !strings.Contains(deployment.ClusterName, cfg.ClusterFilter) { 66 | continue 67 | } 68 | 69 | imageFound := false 70 | for _, container := range deployment.Containers { 71 | if strings.Contains(container.Image.Name.FullName, cfg.ImageNameFilter) { 72 | imageFound = true 73 | continue 74 | } 75 | } 76 | 77 | if !imageFound { 78 | continue 79 | } 80 | 81 | filteredDeployments = append(filteredDeployments, deployment) 82 | } 83 | 84 | stats.DeploymentFilterDuration = time.Now().Sub(start) 85 | stats.FilteredDeploymentExportCount = len(filteredDeployments) 86 | start = time.Now() 87 | 88 | for _, image := range images { 89 | if strings.Contains(image.Name.FullName, cfg.ImageNameFilter) { 90 | filteredImages = append(filteredImages, image) 91 | } 92 | 93 | } 94 | stats.ImageFilterDuration = time.Now().Sub(start) 95 | stats.FilteredImageExportCount = len(filteredImages) 96 | return 97 | } 98 | 99 | var queryMap = map[string]string{} 100 | 101 | func BuildServerQuery(cfg config.ConfigType) string { 102 | var buffer bytes.Buffer 103 | 104 | for k, v := range cfg.QueryStrings() { 105 | if strings.TrimPrefix(v, "r/") != "" { 106 | buffer.WriteString(fmt.Sprintf("%s:%s", k, v)) 107 | buffer.WriteString("+") 108 | } 109 | } 110 | 111 | ret := buffer.String() 112 | 113 | if len(ret) > 0 { 114 | os.Stderr.WriteString(fmt.Sprintf("Server query: %s\n", ret[:len(ret)-1])) 115 | return ret[:len(ret)-1] 116 | } 117 | return "" 118 | } 119 | -------------------------------------------------------------------------------- /acs-export-example/pkg/table/table.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/charmbracelet/lipgloss" 8 | "github.com/charmbracelet/lipgloss/table" 9 | "github.com/pkg/errors" 10 | "golang.org/x/term" 11 | 12 | storage "github.com/stackrox/rox/generated/storage" 13 | ) 14 | 15 | func RenderTable(deployments []*storage.Deployment, images []*storage.Image) error { 16 | imageMap := map[string]*storage.Image{} 17 | 18 | for _, image := range images { 19 | imageMap[image.Name.FullName] = image 20 | } 21 | 22 | width, _, err := term.GetSize(0) 23 | if err != nil { 24 | panic(errors.Wrap(err, "could not get terminal size")) 25 | } 26 | 27 | t := table.New(). 28 | Border(lipgloss.NormalBorder()). 29 | BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("#84A59D"))). 30 | Width(width). 31 | StyleFunc(func(row, col int) lipgloss.Style { 32 | switch { 33 | case row == 0: 34 | return lipgloss.NewStyle().Bold(true) 35 | case row%2 == 0: 36 | return lipgloss.NewStyle().Foreground(lipgloss.Color("#EA9285")) 37 | default: 38 | return lipgloss.NewStyle().Foreground(lipgloss.Color("#F5CAC3")) 39 | } 40 | }). 41 | Headers("CVE", "CVSS", "Cluster", "Namespace", "Image", "Component", "Fixable") 42 | 43 | for _, d := range deployments { 44 | for _, container := range d.Containers { 45 | imageName := container.Image.Name.FullName 46 | 47 | if strings.Contains(imageName, "openshift-release-dev") { 48 | continue 49 | } 50 | 51 | image, found := imageMap[imageName] 52 | if !found || image.Scan == nil { 53 | continue 54 | } 55 | 56 | if len(imageName) > 60 { 57 | imageName = imageName[:57] + "..." 58 | } 59 | 60 | for _, component := range image.Scan.Components { 61 | for _, vuln := range component.Vulns { 62 | fixable := "" 63 | if vuln.GetFixedBy() != "" { 64 | fixable = "fixable" 65 | } 66 | 67 | t.Row(vuln.Cve, fmt.Sprint(vuln.Cvss), d.ClusterName, d.Namespace, imageName, component.Name, fixable) 68 | } 69 | } 70 | } 71 | } 72 | 73 | fmt.Println(t) 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /acs-export-example/readme.md: -------------------------------------------------------------------------------- 1 | # ACS/StackRox Export CLI 2 | 3 | ![Made with VHS](https://vhs.charm.sh/vhs-6oVYwxgou22QgkIgZackdK.gif) 4 | 5 | A CLI that generates a CVE report based on the ACS export APIs. 6 | 7 | ``` 8 | $ ./acs-export-example -h 9 | CLI to browse data pulled from ACS (Advanced Cluster Security) (i.e. StackRox). 10 | 11 | Usage: 12 | acs-export-example [flags] 13 | 14 | Flags: 15 | -c, --cluster string Cluster client-side filter. 16 | -t, --filter-type string Where to do the param-based filtering. Available options: [client, server] (default "client") 17 | -f, --fixable string Filter on whether a cve is fixable. Available options: [true, false, ""]. 18 | -h, --help help for acs-export-example 19 | -i, --image string Image name client-side filter. 20 | -n, --namespace string Namespace client-side filter. 21 | -o, --output string Output format. Available options: [table, csv] (default "table") 22 | -q, --query string Pass a query string to the server. Incompatible with --filter-type=server 23 | -s, --stats Print stats about the export 24 | -v, --vuln string Vulnerability client-side filter. 25 | ``` 26 | 27 | ## Setup 28 | 29 | Currently the only way to configure the Central endpoint is to set these two environment variables: 30 | 31 | export ROX_ENDPOINT=https://central.stackrox.com 32 | export ROX_API_TOKEN=eyJh... # base64-encoded JWT 33 | 34 | ## Output Formats 35 | 36 | Print a table to the console with all reported vulnerabilities: 37 | 38 | $ ./acs-export-example 39 | 40 | Output in CSV format instead: 41 | 42 | $ ./acs-export-example -o csv 43 | 44 | ## Filters 45 | 46 | Filter by cluster: 47 | 48 | $ ./acs-export-example -c acs-stage-cluster 49 | 50 | Filter by cluster and namespace: 51 | 52 | $ ./acs-export-example -c acs-stage-cluster -n stackrox 53 | 54 | Filter by CVE/vulnerability name: 55 | 56 | $ ./acs-export-example -v CVE-2021-0000 57 | 58 | Filter by image name: 59 | 60 | $ ./acs-export-example -i quay.io/kylape/my-image-repo 61 | 62 | Filter on whether a vulnerability is considered fixable or not: 63 | 64 | $ ./acs-export-example -f true # Only output fixable vulns 65 | 66 | The above filters are performed on the client side by default. 67 | Use the `--filter-type=server` option to instead build a query string to have the filters executed on the server: 68 | 69 | $ ./acs-export-example -i quay.io/kylape/my-image-repo -t server 70 | 71 | If you'd rather build the query string for the server yourself: 72 | 73 | $ ./acs-export-example -q "CLUSTER:acs-stage-cluster+NAMESPACE:stackrox+CVE:r/CVE-2021-0000" 74 | 75 | ## Stats 76 | 77 | The `--stats` option prints various timings and counts that may be interesting to use for performance analysis: 78 | 79 | ``` 80 | $ ./acs-export-example -o csv -s > export.csv 81 | Fetching deployments 82 | Fetching images 83 | Durations: 84 | Connect: 295.686µs 85 | Deployment Export: 611.136684ms 86 | Image Export: 4.048604959s 87 | Deployment Filtering: 47.291µs 88 | Image Filtering: 2.547318ms 89 | 90 | Counts: 91 | Deployments: 444 92 | Images: 375 93 | Filtered Deployments: 444 94 | Filtered Images: 353 95 | ``` 96 | 97 | ## Building 98 | 99 | ``` 100 | go build 101 | ``` 102 | -------------------------------------------------------------------------------- /api-examples/README.md: -------------------------------------------------------------------------------- 1 | # ACS API Examples 2 | 3 | ### Examples of API usage to perform configuration and reporting tasks with Red Hat Advanced Cluster Security 4 | 5 | These examples use an ACS API Token [that you can issue from the ACS Central Integrations page](https://docs.openshift.com/acs/3.71/cli/getting-started-cli.html#cli-authentication_cli-getting-started) 6 | 7 | --- 8 | 9 | The examples assume two environment variables:
10 | CENTRAL is the exposed hostname or IP address of the ACS Central pod's route or loadbalancer
11 | ROX_API_TOKEN is the full text of a token created in the Central UI's Integrations page
12 | 13 | API calls to ACS' RESTful API endpoints require the API token in the Authorization: Bearer header. The RESTful API documentation is available in the Central UI. 14 | 15 | ``` 16 | export CENTRAL=central-stackrox.apps.example.com 17 | export ROX_API_TOKEN=eyJhbGciOiJSUzI1NiIsIm...HYkJj2uWo 18 | ``` 19 | 20 | --- 21 | 22 | Absurdly simple example: 23 | ``` 24 | curl -k -H "Authorization: Bearer ${ROX_API_TOKEN}" https://$CENTRAL:443/v1/ping 25 | ``` 26 | -------------------------------------------------------------------------------- /api-examples/authprovider-minrole.md: -------------------------------------------------------------------------------- 1 | This is a two-stage process to add the auth provider and the minimum role with the API.
2 | 3 | this is some shell script shorthand: 4 | ```bash 5 | OPENSHIFT_AUTH='{"id":"","name":"OpenShift","type":"openshift","config":{},"uiEndpoint":"'"${CENTRAL}"'","enabled":true}' 6 | CURLEXEC=$( curl -s -k -H "Authorization: Bearer ${ROX_API_TOKEN}" --header "Content-Type: application/json" -X POST "https://${CENTRAL}/v1/authProviders" -d "$OPENSHIFT_AUTH" ) 7 | ``` 8 | 9 | CENTRAL is the route hostname for your Central app 10 | and then the min access role: 11 | ```bash 12 | # add minimum access role 13 | MIN_ROLE='{"previous_groups":[],"required_groups":[{"props":{"authProviderId":"'"${AUTH_ID}"'"},"roleName":"Analyst"}]}' 14 | curl -k -H "Authorization: Bearer ${ROX_API_TOKEN}" --header "Content-Type: application/json" -X POST "https://${CENTRAL}/v1/groupsbatch" -d "$MIN_ROLE" 15 | ``` 16 | 17 | here's a naive but complete script: 18 | ```bash 19 | CENTRAL=central.example.com 20 | CENTRAL_PASS="AdminPassForCentral" 21 | 22 | # get an API token; not needed if already available 23 | POLICY_JSON='{ "name": "mytoken", "role":"Admin"}' 24 | APIURL="https://$CENTRAL/v1/apitokens/generate" 25 | ROX_API_TOKEN=$(curl -s -k -u admin:$CENTRAL_PASS -H 'Content-Type: application/json' -X POST -d "$POLICY_JSON" "$APIURL" | jq -r '.token') 26 | 27 | # create the openshift auth SSO 28 | OPENSHIFT_AUTH='{"id":"","name":"OpenShift","type":"openshift","config":{},"uiEndpoint":"'"${CENTRAL}"'","enabled":true}' 29 | CURLEXEC=$( curl -s -k -H "Authorization: Bearer ${ROX_API_TOKEN}" --header "Content-Type: application/json" -X POST "https://${CENTRAL}/v1/authProviders" -d "$OPENSHIFT_AUTH" ) 30 | 31 | echo $CURLEXEC 32 | 33 | AUTH_ID=echo $CURLEXEC | jq -r '.id' 34 | 35 | echo "Created integration with ID $AUTH_ID" 36 | echo 37 | 38 | # add minimum access role 39 | MIN_ROLE='{"previous_groups":[],"required_groups":[{"props":{"authProviderId":"'"${AUTH_ID}"'"},"roleName":"Analyst"}]}' 40 | curl -k -H "Authorization: Bearer ${ROX_API_TOKEN}" --header "Content-Type: application/json" -X POST "https://${CENTRAL}/v1/groupsbatch" -d "$MIN_ROLE" 41 | echo 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /api-examples/delete_group.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | curl -k -H "Authorization: Bearer ${ROX_API_TOKEN}" --header "Content-Type: application/json" -X DELETE "https://ce$CENTRAL/v1/groups?authProviderId=blah&key=groups&value=blah&id=io.stackrox.authz.group.d753bd68-6769-4858-8d34-d996461812dc" 3 | ``` 4 | -------------------------------------------------------------------------------- /api-examples/groupsbatch_newgroups.md: -------------------------------------------------------------------------------- 1 | We can do a batch update to add groups in the following manner: 2 | 3 | 4 | As always, we export the Central address and your API token: 5 | ``` 6 | export CENTRAL=YOUR_CENTRAL_ADDRESS 7 | ``` 8 | ``` 9 | export ROX_API_TOKEN=eyJhb....BlAh 10 | ``` 11 | Retrive your AuthProviderID: 12 | ``` 13 | curl -k -H "Authorization: Bearer ${ROX_API_TOKEN}" https://$CENTRAL/v1/authProviders 14 | ``` 15 | ``` 16 | export AUTH_ID=Id_from_previous_curl 17 | ``` 18 | 19 | 20 | Utilizing your AuthProvider ID, you can define utilize the "value" field to map your groups to the needed roles. 21 | ```bash 22 | curl -k -H "Authorization: Bearer ${ROX_API_TOKEN}" --header "Content-Type: application/json" -X POST "https://$CENTRAL/v1/groupsbatch" -d '{ "requiredGroups": [{"props":{"id":"","authProviderId":"$AUTH_ID","key":"groups","value":"LDAP_Analyst_Group"},"roleName":"Analyst"},{"props":{"id":"","authProviderId":"$AUTH_ID","key":"groups","value":"LDAP_Analyst_Group_2"},"roleName":"Analyst"}]}' 23 | ``` 24 | -------------------------------------------------------------------------------- /api-examples/groupservice_creategroup.md: -------------------------------------------------------------------------------- 1 | Creating a single group can be achieved by providing a key, value pair and mapping it to a role. 2 | 3 | ```json 4 | { 5 | "props": { 6 | "id": "string", 7 | "authProviderId": "string", 8 | "key": "string", 9 | "value": "string" 10 | }, 11 | "roleName": "string" 12 | } 13 | ``` 14 | 15 | ```bash 16 | curl -k -H "Authorization: Bearer ${ROX_API_TOKEN}" --header "Content-Type: application/json" -X POST "https://$CENTRAL/v1/groups" -d '{"props":{"id":"","authProviderId":"38c7afcd-d943-4163-bdb0-7787f9cdb3a4","key":"groups","value":"LDAP_Analyst_Group"},"roleName":"Analyst"}' 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /api-examples/simple-alerts.md: -------------------------------------------------------------------------------- 1 | #Simple examples of ACS "alert" objects, the structure behind Violations in the UI 2 | 3 | Simple curl examples 4 | 5 | These examples all use environment variables for the Hostname of ACS Central (Control Plane) and for the contents of a StackRox API token that you can create from the UI under Platform Integration -> Integrations. 6 | 7 | 8 | Super simple alert retrieval: 9 | ```bash 10 | curl -k -H "Authorization: Bearer ${TOKEN}" https://$CENTRAL/v1/alerts | jq -r '.' 11 | ``` 12 | 13 | Using a search query for alerts: 14 | ```bash 15 | curl -k -H "Authorization: Bearer ${TOKEN}" https://$CENTRAL/v1/alerts?query="Namespace:test" | jq -r '.' 16 | ``` 17 | 18 | Combination search query: 19 | ```bash 20 | curl -k -H "Authorization: Bearer ${TOKEN}" https://$CENTRAL/v1/alerts?query="Cluster:kube+Namespace:stackrox,kube-system" | jq -r '.' 21 | ``` 22 | 23 | Combination search query with URL-safe encoding: 24 | ```bash 25 | curl -k -H "Authorization: Bearer ${TOKEN}" https://$CENTRAL/v1/alerts?query=Severity%3AHIGH_SEVERITY%2BNamespace%3Apayments | jq -r '.' 26 | ``` 27 | 28 | Search filter for time range: 29 | ```bash 30 | curl -k -H "Authorization: Bearer ${TOKEN}" https://$CENTRAL/v1/alerts?query==Violation%20Time%3A%3E1d | jq -r '.' 31 | ``` 32 | -------------------------------------------------------------------------------- /backups/api-key-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: rox-api-token 5 | stringData: 6 | token: ### insert api token generated by stackrox 7 | -------------------------------------------------------------------------------- /backups/cron-backup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: backup-cron 5 | namespace: stackrox 6 | spec: 7 | concurrencyPolicy: Allow 8 | failedJobsHistoryLimit: 1 9 | jobTemplate: 10 | metadata: 11 | creationTimestamp: null 12 | spec: 13 | template: 14 | metadata: 15 | creationTimestamp: null 16 | spec: 17 | containers: 18 | - args: 19 | - --output 20 | - /mnt 21 | - -e 22 | - ###insert end point here or be professional and use a variable 23 | - --insecure-skip-tls-verify 24 | command: 25 | - roxctl 26 | - central 27 | - backup 28 | env: ###either retreieve the rox pass or create a token and store it in a secret the set the env, don't use passwords in Git! 29 | - name: ROX_API_TOKEN 30 | valueFrom: 31 | secretKeyRef: 32 | key: token 33 | name: rox-api-token 34 | image: registry.redhat.io/advanced-cluster-security/rhacs-roxctl-rhel8:3.71.2 35 | imagePullPolicy: IfNotPresent 36 | name: backup-cron 37 | resources: {} 38 | terminationMessagePath: /dev/termination-log 39 | terminationMessagePolicy: File 40 | volumeMounts: 41 | - mountPath: /mnt 42 | name: stackrox-backups-uat 43 | dnsPolicy: ClusterFirst 44 | restartPolicy: OnFailure 45 | schedulerName: default-scheduler 46 | securityContext: {} 47 | terminationGracePeriodSeconds: 30 48 | volumes: 49 | - name: stackrox-backups-uat 50 | persistentVolumeClaim: 51 | claimName: stackrox-backups-uat 52 | schedule: 05 1 * * * 53 | successfulJobsHistoryLimit: 3 54 | suspend: false 55 | -------------------------------------------------------------------------------- /backups/cron-clean-backup.yaml: -------------------------------------------------------------------------------- 1 | kind: CronJob 2 | apiVersion: batch/v1 3 | metadata: 4 | name: clean-backup-cron 5 | namespace: stackrox 6 | spec: 7 | schedule: 10 1 * * * 8 | concurrencyPolicy: Allow 9 | suspend: false 10 | jobTemplate: 11 | metadata: 12 | creationTimestamp: null 13 | spec: 14 | template: 15 | metadata: 16 | creationTimestamp: null 17 | spec: 18 | volumes: 19 | - name: stackrox-backups-uat 20 | persistentVolumeClaim: 21 | claimName: stackrox-backups-uat 22 | containers: 23 | - name: clean-backup-cron 24 | image: registry.access.redhat.com/ubi8/ubi 25 | args: 26 | - /bin/sh 27 | - '-c' 28 | - 'find /mnt* -mtime +30 -exec rm {} \;' 29 | resources: {} 30 | volumeMounts: 31 | - name: stackrox-backups-uat 32 | mountPath: /mnt 33 | terminationMessagePath: /dev/termination-log 34 | terminationMessagePolicy: File 35 | imagePullPolicy: IfNotPresent 36 | restartPolicy: OnFailure 37 | terminationGracePeriodSeconds: 30 38 | dnsPolicy: ClusterFirst 39 | securityContext: {} 40 | schedulerName: default-scheduler 41 | successfulJobsHistoryLimit: 3 42 | failedJobsHistoryLimit: 1 43 | status: {} 44 | 45 | -------------------------------------------------------------------------------- /backups/readme.md: -------------------------------------------------------------------------------- 1 | This is a simple example of running ACS/Stackrox backups in a container and storing them on a persistent volume. 2 | 3 | api-key-secret.yaml creates a secret from a generated rox api token 4 | - you must fill the token value in with a stackrox api token. You can see how to do that [here](https://access.redhat.com/documentation/en-us/red_hat_advanced_cluster_security_for_kubernetes/3.71/html-single/roxctl_cli/index#cli-authentication_cli-getting-started) 5 | 6 | cron-backups.yaml creates a container from the roxctl image and runs a backup storing it in a PVC mounted on /mnt 7 | - You can utilize ROX_CENTRAL_ADDRESS as an env variable as well 8 | - The above env variables were not used in this case with hopes of showing simple examples that could be built upon. 9 | 10 | retreive-backups-pod.yaml creates a simple container that mounts a PVC where you could retrieve a backup, if needed. (obviously you need to mount the correct PVC) 11 | 12 | There are many ways to achieve the backup task. This was done for an environment where access was limited and the ACS team only had access to persistent storage via the cluster. 13 | -------------------------------------------------------------------------------- /backups/retrieve-backups-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: retrieve-backups 5 | namespace: stackrox 6 | spec: 7 | containers: 8 | - name: retrieve-backups 9 | image: 'registry.access.redhat.com/ubi8/ubi' 10 | volumeMounts: 11 | - mountPath: /mnt 12 | name: stackrox-backups-uat 13 | command: [ "/bin/bash", "-c", "--" ] 14 | args: [ "while true; do sleep 30; done;" ] 15 | volumes: 16 | - name: stackrox-backups-uat 17 | persistentVolumeClaim: 18 | claimName: stackrox-backups-uat 19 | -------------------------------------------------------------------------------- /ci/Azure DevOps/Pipelines/README.md: -------------------------------------------------------------------------------- 1 | # Azure DevOps Pipeline Sample 2 | 3 | This sample is a fragment of an azure-pipelines.yml file that downloads `roxctl` and uses it to scan an image, check it against configured system policies, and generate a CSV with all packages & vulnerabilities broken down by the layer in which they were introduced. It saves all this output as artifacts of the build. 4 | 5 | In order to use this sample, you should set two variables in your Azure DevOps project: 6 | 7 | * roxcentralendpoint -- this is the exposed address & port for your StackRox Central deployment in the form `stackrox.contoso.com:443`. 8 | * roxapitoken -- this is an API token with at least CI privileges. 9 | 10 | Change `vulnerables/cve-2017-7494` to match the image you want to scan as part of the build. 11 | 12 | OWNER: neilcar 13 | 14 | LATEST TESTED: 3.0.53.0 15 | -------------------------------------------------------------------------------- /ci/Azure DevOps/Pipelines/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | resources: 5 | - repo: self 6 | 7 | stages: 8 | - stage: Scan 9 | jobs: 10 | - job: Scan 11 | displayName: Scan with StackRox 12 | pool: 13 | vmImage: 'ubuntu-latest' 14 | steps: 15 | - script: | 16 | mkdir $(System.DefaultWorkingDirectory)/artifacts 17 | curl -k -L -H "Authorization: Bearer $ROX_API_TOKEN" https://$ROX_CENTRAL_ENDPOINT/api/cli/download/roxctl-linux --output ./roxctl 18 | chmod +x ./roxctl 19 | ./roxctl image scan -e $ROX_CENTRAL_ENDPOINT --image vulnerables/cve-2017-7494 --format csv > $(System.DefaultWorkingDirectory)/artifacts/image_scan.csv 20 | ./roxctl image check -e $ROX_CENTRAL_ENDPOINT --image vulnerables/cve-2017-7494 > $(System.DefaultWorkingDirectory)/artifacts/image_check.txt 21 | displayName: 'StackRox image scan' 22 | env: 23 | ROX_API_TOKEN: $(roxapitoken) 24 | ROX_CENTRAL_ENDPOINT: '$(roxcentralendpoint)' 25 | - task: PublishPipelineArtifact@1 26 | displayName: 'Publish Pipeline Artifact' 27 | inputs: 28 | targetPath: '$(System.DefaultWorkingDirectory)/artifacts' 29 | artifact: 'StackRox Output' 30 | condition: succeededOrFailed() 31 | -------------------------------------------------------------------------------- /ci/CircleCI/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # StackRox inline orb sample 2 | # author neil@stackrox.com 3 | 4 | version: 2.1 5 | orbs: 6 | rox-orb: 7 | jobs: 8 | rox-deployment-check: 9 | machine: true 10 | parameters: 11 | rox_api_token: 12 | description: API key with CI permissions 13 | type: string 14 | rox_central_endpoint: 15 | description: URL of Central (central.contoso.com:443 for example) 16 | type: string 17 | rox_deployment: 18 | description: Path/name of yaml to check 19 | type: string 20 | steps: 21 | - checkout 22 | - rox-roxctl-install: 23 | rox_api_token: <> 24 | rox_central_endpoint: <> 25 | - run: >- 26 | ./roxctl deployment check -e <> --file <> 27 | rox-image-scan: 28 | machine: true 29 | parameters: 30 | rox_api_token: 31 | description: API key with CI permissions 32 | type: string 33 | rox_central_endpoint: 34 | description: URL of Central (central.contoso.com:443 for example) 35 | type: string 36 | rox_image: 37 | description: Name of image to scan (neilcar/testimage:5 or registry.contoso.com/db_broker:latest for example) 38 | type: string 39 | output_format: 40 | description: Format of image scan output (table | csv | json) (default "json") 41 | type: string 42 | default: "json" 43 | steps: 44 | - rox-roxctl-install: 45 | rox_api_token: <> 46 | rox_central_endpoint: <> 47 | - run: >- 48 | ./roxctl image scan -e <> --image <> --output <> 49 | rox-image-check: 50 | machine: true 51 | parameters: 52 | rox_api_token: 53 | description: API key with CI permissions 54 | type: string 55 | rox_central_endpoint: 56 | description: URL of Central (central.contoso.com:443 for example) 57 | type: string 58 | rox_image: 59 | description: Name of image to check (neilcar/testimage:5 or registry.contoso.com/db_broker:latest for example) 60 | type: string 61 | output_format: 62 | description: Format of image scan output (table | csv | json | junit) (default "table") 63 | type: string 64 | default: "table" 65 | steps: 66 | - rox-roxctl-install: 67 | rox_api_token: <> 68 | rox_central_endpoint: <> 69 | - run: >- 70 | ./roxctl image check -e <> --image <> --output <> 71 | commands: 72 | rox-roxctl-install: 73 | parameters: 74 | rox_api_token: 75 | description: API key with CI permissions 76 | type: string 77 | rox_central_endpoint: 78 | description: URL of Central (central.contoso.com:443 for example) 79 | type: string 80 | steps: 81 | - run: >- 82 | curl -k -L -H "Authorization: Bearer <>" https://<>/api/cli/download/roxctl-linux --output ./roxctl 83 | - run: chmod +x ./roxctl 84 | 85 | workflows: 86 | scanimage: 87 | when: always 88 | jobs: 89 | - rox-orb/rox-image-scan: 90 | context: rox 91 | name: scan an image 92 | rox_central_endpoint: $ROX_CENTRAL_ENDPOINT 93 | rox_api_token: $ROX_API_TOKEN 94 | rox_image: neilcar/jenkins-demo:latest 95 | - rox-orb/rox-image-check: 96 | context: rox 97 | name: check an image against build-time policies 98 | rox_central_endpoint: $ROX_CENTRAL_ENDPOINT 99 | rox_api_token: $ROX_API_TOKEN 100 | rox_image: neilcar/jenkins-demo:latest 101 | - rox-orb/rox-deployment-check: 102 | context: rox 103 | name: Check deployment 104 | rox_central_endpoint: $ROX_CENTRAL_ENDPOINT 105 | rox_api_token: $ROX_API_TOKEN 106 | rox_deployment: deploy.yml 107 | 108 | -------------------------------------------------------------------------------- /ci/CircleCI/README.md: -------------------------------------------------------------------------------- 1 | # CircleCI Orb Sample 2 | 3 | 4 | This sample uses an inline orb that exposes three jobs 5 | 6 | ``` 7 | rox-image-check: 8 | description: does a `roxctl image check` 9 | parameters: 10 | rox_api_token: 11 | description: API key with CI permissions 12 | type: string 13 | rox_central_endpoint: 14 | description: URL of Central (central.contoso.com:443 for example) 15 | type: string 16 | rox_image: 17 | description: Name of image to check (neilcar/testimage:5 or registry.contoso.com/db_broker:latest for example) 18 | type: string 19 | output_format: 20 | description: Format of image scan output (table | csv | json | junit) (default "table") 21 | type: string 22 | ``` 23 | ``` 24 | job: rox-image-scan 25 | description: does a `roxctl image scan` 26 | parameters: 27 | rox_api_token: 28 | description: API key with CI permissions 29 | type: string 30 | rox_central_endpoint: 31 | description: URL of Central (central.contoso.com:443 for example) 32 | type: string 33 | rox_deployment: 34 | description: Path/name of yaml to check 35 | type: string 36 | output_format: 37 | description: Format of image scan output (table | csv | json) (default "json") 38 | type: string 39 | ``` 40 | ``` 41 | job: rox-deployment-check 42 | description: does a `roxctl deployment check` 43 | parameters: 44 | rox_api_token: 45 | description: API key with CI permissions 46 | type: string 47 | rox_central_endpoint: 48 | description: URL of Central (central.contoso.com:443 for example) 49 | type: string 50 | rox_deployment: 51 | description: Path/name of yaml to check 52 | type: string 53 | ``` 54 | 55 | OWNER: neilcar 56 | 57 | LATEST TESTED: 3.68.1 58 | -------------------------------------------------------------------------------- /ci/CircleCI/deploy.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: nginx 5 | labels: 6 | app: nginx 7 | spec: 8 | containers: 9 | - name: nginx 10 | image: nginx 11 | ports: 12 | - containerPort: 80 13 | securityContext: 14 | privileged: true 15 | -------------------------------------------------------------------------------- /ci/GitHub/kube-linter/.github/actions/kube-lint/action.yml: -------------------------------------------------------------------------------- 1 | name: 'kube-linter' 2 | description: 'Scan directory with kube-linter' 3 | inputs: 4 | directory: 5 | description: 'Directory to scan ' 6 | required: true 7 | config: 8 | description: 'Path to config file' 9 | required: false 10 | outputs: 11 | lint-txt: 12 | description: 'Output from linting' 13 | value: ${{ steps.lint.outputs.lint-txt }} 14 | runs: 15 | using: "composite" 16 | steps: 17 | - name: Download latest kube-linter 18 | run: | 19 | LOCATION=$(curl -s https://api.github.com/repos/stackrox/kube-linter/releases/latest \ 20 | | grep "tag_name" \ 21 | | awk '{print "https://github.com/stackrox/kube-linter/releases/download/" substr($2, 2, length($2)-3) "/kube-linter-linux.tar.gz"}') 22 | curl -s -L -o kube-linter-linux.tar.gz $LOCATION 23 | tar -xf kube-linter-linux.tar.gz -C "${GITHUB_WORKSPACE}/" 24 | shell: bash 25 | - id: lint 26 | run: | 27 | cd "${GITHUB_WORKSPACE}" 28 | if [ -z ${{ inputs.config }} ]; then 29 | export CONFIG="" 30 | else 31 | export CONFIG="--config ${{ inputs.config }}" 32 | fi 33 | ./kube-linter $CONFIG lint ${{ inputs.directory }} 34 | shell: bash 35 | 36 | -------------------------------------------------------------------------------- /ci/GitHub/kube-linter/.github/workflows/kube-linter.yml: -------------------------------------------------------------------------------- 1 | name: Check Kubernetes YAMLs 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | scan: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Scan repo 15 | id: kube-lint-repo 16 | uses: ./.github/actions/kube-lint 17 | with: 18 | directory: yamls 19 | config: .kube-linter/config.yaml 20 | 21 | -------------------------------------------------------------------------------- /ci/GitHub/kube-linter/.kube-linter/config.yaml: -------------------------------------------------------------------------------- 1 | # customChecks defines custom checks. 2 | customChecks: 3 | - name: "required-label-app" 4 | template: "required-label" 5 | params: 6 | key: "app" 7 | checks: 8 | # if doNotAutoAddDefaults is true, default checks are not automatically added. 9 | doNotAutoAddDefaults: false 10 | # addAllBuiltIn, if set, adds all built-in checks. This allows users to 11 | # explicitly opt-out of checks that are not relevant using Exclude. 12 | # Takes precedence over doNotAutoAddDefaults, if both are set. 13 | addAllBuiltIn: false 14 | # include explicitly adds checks, by name. You can reference any of the built-in checks. 15 | # Note that customChecks defined above are included automatically. 16 | include: 17 | - "required-label-owner" 18 | # exclude explicitly excludes checks, by name. exclude has the highest priority: if a check is 19 | # in exclude, then it is not considered, even if it is in include as well. 20 | exclude: 21 | - "privileged" 22 | -------------------------------------------------------------------------------- /ci/GitHub/kube-linter/README.md: -------------------------------------------------------------------------------- 1 | # kube-lint GitHub action 2 | 3 | This is a GitHub action for scanning Kubernetes deployment files with [kube-linter](https://github.com/stackrox/kube-linter). This includes both the action itself (.github/actions/kubelint) and sample GitHub workflow (.github/workflows) and a test YAML. 4 | 5 | Quick deployment: 6 | 7 | 1. Create a new GitHub repo. 8 | 2. Push all files from this sample into the repo. 9 | 3. The `kube-linter.yml` workflow will run as an action every time there's a new push to this repo. 10 | 11 | More info on creating a repo with a GitHub action like this and using it broadly can be [found here](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/creating-a-composite-run-steps-action) 12 | 13 | The action takes two parameters. 14 | 15 | ``` 16 | - name: Scan repo 17 | id: kube-lint-repo 18 | uses: ./.github/actions/kube-lint 19 | with: 20 | directory: yamls 21 | config: .kube-linter/config.yaml 22 | ``` 23 | 24 | * `directory` is mandatory -- this is the directory of deployment files to scan. 25 | * `config` is optional -- this is the path to a [configuration file](https://github.com/stackrox/kube-linter/blob/main/config.yaml.example) if you wish to use a non-default configuration. 26 | 27 | LATEST TESTED VERSION: 0.1.1 28 | -------------------------------------------------------------------------------- /ci/GitHub/kube-linter/yamls/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | labels: 6 | app: nginx 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: nginx 12 | template: 13 | metadata: 14 | labels: 15 | app: nginx 16 | spec: 17 | containers: 18 | - name: nginx 19 | image: nginx:1.14.2 20 | ports: 21 | - containerPort: 80 -------------------------------------------------------------------------------- /ci/GitHub/stackrox-action/.github/actions/stackrox-check/action.yml: -------------------------------------------------------------------------------- 1 | name: 'stackrox-scan' 2 | description: 'Scan image with StackRox' 3 | inputs: 4 | central-endpoint: 5 | description: 'Central endpoint in the format stackrox.contoso.com:443' 6 | required: true 7 | api-token: 8 | description: 'API token with CI permissions' 9 | required: true 10 | image: 11 | description: 'Full name of the image to scan -- gcr.io/stackrox/testimage:5.1' 12 | required: true 13 | runs: 14 | using: "composite" 15 | steps: 16 | - run: > 17 | curl -s -k -L -H "Authorization: Bearer ${{ inputs.api-token }}" https://${{ inputs.central-endpoint }}/api/cli/download/roxctl-linux --output ./roxctl 18 | shell: bash 19 | - run: chmod +x ./roxctl 20 | shell: bash 21 | - id: scan-check 22 | run: | 23 | ./roxctl image check --force -e ${{ inputs.central-endpoint }} --image ${{ inputs.image }} 24 | env: 25 | ROX_API_TOKEN: ${{ inputs.api-token }} 26 | shell: bash 27 | -------------------------------------------------------------------------------- /ci/GitHub/stackrox-action/.github/actions/stackrox-scan/action.yml: -------------------------------------------------------------------------------- 1 | name: 'stackrox-scan' 2 | description: 'Scan image with StackRox' 3 | inputs: 4 | central-endpoint: 5 | description: 'Central endpoint in the format stackrox.contoso.com:443' 6 | required: true 7 | api-token: 8 | description: 'API token with CI permissions' 9 | required: true 10 | image: 11 | description: 'Full name of the image to scan -- gcr.io/stackrox/testimage:5.1' 12 | required: true 13 | format: 14 | description: 'Format of output. Valid values are json, csv, and pretty' 15 | required: 'false' 16 | default: 'pretty' 17 | runs: 18 | using: "composite" 19 | steps: 20 | - run: > 21 | curl -s -k -L -H "Authorization: Bearer ${{ inputs.api-token }}" https://${{ inputs.central-endpoint }}/api/cli/download/roxctl-linux --output ./roxctl 22 | shell: bash 23 | - run: chmod +x ./roxctl 24 | shell: bash 25 | - id: scan-info 26 | run: | 27 | ./roxctl image scan -e ${{ inputs.central-endpoint }} --image ${{ inputs.image }} --format ${{ inputs.format }} 28 | env: 29 | ROX_API_TOKEN: ${{ inputs.api-token }} 30 | shell: bash 31 | -------------------------------------------------------------------------------- /ci/GitHub/stackrox-action/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | jobs: 4 | scan: 5 | runs-on: ubuntu-latest 6 | name: Scan image 7 | steps: 8 | - uses: actions/checkout@v2 9 | - id: scan 10 | uses: ./.github/actions/stackrox-scan 11 | with: 12 | image: 'vulnerables/cve-2017-7494' 13 | central-endpoint: ${{ secrets.ROX_CENTRAL_ENDPOINT }} 14 | api-token: ${{ secrets.ROX_API_TOKEN }} 15 | - id: check 16 | uses: ./.github/actions/stackrox-check 17 | with: 18 | image: 'vulnerables/cve-2017-7494' 19 | central-endpoint: ${{ secrets.ROX_CENTRAL_ENDPOINT }} 20 | api-token: ${{ secrets.ROX_API_TOKEN }} 21 | -------------------------------------------------------------------------------- /ci/GitHub/stackrox-action/README.md: -------------------------------------------------------------------------------- 1 | # StackRox GitHub actions 2 | 3 | This is a GitHub action for scanning Docker images and checking them against policies. This sample includes both the actions themselves (.github/actions) and a sample workflow (.github/workflows) 4 | 5 | Quick deployment: 6 | 7 | 1. Create a new GitHub repo. 8 | 2. Push all files from this sample into the repo. 9 | 3. The `main.yml` workflow will run as an action every time there's a new push to this repo. 10 | 11 | More info on creating a repo with a GitHub action like this and using it broadly can be [found here](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/creating-a-composite-run-steps-action) 12 | 13 | There are two actions in this sample -- `stackrox-scan` and `stackrox-check`. 14 | 15 | `stackrox-scan` returns the JSON with the full analysis of the image composition including components & vulnerabilities. This is useful for getting everything StackRox knows about the image. 16 | 17 | `stackrox-check` tests the image against configured policies. If any violated policies are enforced, this will pass a non-zero return code back, causing the build to fail. 18 | 19 | Both actions take the same parameters: 20 | 21 | ``` 22 | - id: check 23 | uses: ./.github/actions/stackrox-check 24 | with: 25 | image: 'vulnerables/cve-2017-7494' 26 | central-endpoint: ${{ secrets.ROX_CENTRAL_ENDPOINT }} 27 | api-token: ${{ secrets.ROX_API_TOKEN }} 28 | ``` 29 | 30 | All parameters are mandatory. You should store the API token and the endpoint for Central (in the format `stackrox.contoso.com:443`) in [GitHub encrypted secrets](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets). 31 | 32 | * `image` is the image to be scanned -- `vulnerables/cve-2017-7494` for example. 33 | * `central-endpoint` is the location of the StackRox Central deployment. 34 | * `api-token` is the StackRox API token to be used. This token must have at least CI privileges. 35 | 36 | LATEST TESTED VERSION: 3.0.53.0 37 | -------------------------------------------------------------------------------- /ci/GitLab/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | scan: 2 | stage: test 3 | script: 4 | - "curl -k -L -H \"Authorization: Bearer $ROX_API_TOKEN\" https://$ROX_CENTRAL_ENDPOINT/api/cli/download/roxctl-linux --output ./roxctl " 5 | - chmod a+x ./roxctl 6 | - ./roxctl image scan -e $ROX_CENTRAL_ENDPOINT --force --image my.registry/repo/image:latest 7 | - ./roxctl image check -e $ROX_CENTRAL_ENDPOINT --image my.registry/repo/image:latest 8 | -------------------------------------------------------------------------------- /ci/GitLab/README.md: -------------------------------------------------------------------------------- 1 | # GitLab Sample 2 | 3 | 4 | This sample is a fragment of a .gitlab-ci.yml file that downloads `roxctl` and uses it to scan an image & check it against configured system policies. 5 | 6 | In order to use this sample, you should set two environment variables in your GitLab project: 7 | 8 | * ROX_CENTRAL_ENDPOINT -- this is the exposed address & port for your StackRox Central deployment in the form `stackrox.contoso.com:443`. 9 | * ROX_API_TOKEN -- this is an API token with at least CI privileges. 10 | 11 | Change `my.registry/repo/image:latest` to match the image you want to scan as part of the build. 12 | 13 | OWNER: neilcar 14 | 15 | LATEST TESTED: 3.0.51.0 -------------------------------------------------------------------------------- /ci/Tekton/README.md: -------------------------------------------------------------------------------- 1 | # Tekton / OpenShift Pipelines Sample 2 | 3 | ## Overview 4 | 5 | This sample includes ClusterTasks for: 6 | 7 | |ClusterTask|Description| 8 | |---|---| 9 | |`rox-image-scan`|Scan an image and return results formatted as json, csv, or human-readable| 10 | |`rox-image-check`|Check an image against build-time policies and return success/failure| 11 | |`rox-deployment-check`|Check a deployment yaml against deploy-time policies and return success/failure}| 12 | 13 | It also includes samples for creating secrets for the StackRox Central endpoint & API token as well as pipelines that utilize the image scanning & checking ClusterTasks. 14 | 15 | ## Installation & Testing 16 | 17 | Use `kubectl apply -f Tasks/` or `oc apply -f Tasks/` to create the ClusterTasks for use in the cluster. 18 | 19 | To use the samples, edit `rox-secrets.yml` to include the correct values, create a `pipeline-demo` namespace (or change the namespace in the deployment files in `Sample/`), and run `kubectl apply -f Sample/` / `oc apply -f Sample/`. To run the pipeline, trigger it from the Web UI or use `tkn pipeline start rox-pipeline -n pipeline-demo -p image=vulnerables/phpldapadmin-remote-dump` (passing in the image to be scanned in the `image` parameter). -------------------------------------------------------------------------------- /ci/Tekton/Sample/rox-pipeline.yml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: Pipeline 3 | metadata: 4 | name: rox-pipeline 5 | namespace: pipeline-demo 6 | spec: 7 | description: Rox demo pipeline 8 | params: 9 | - name: image 10 | type: string 11 | description: Full name of image to scan (example -- gcr.io/rox/sample:5.0-rc1) 12 | tasks: 13 | - name: image-scan 14 | taskRef: 15 | name: rox-image-scan 16 | kind: ClusterTask 17 | params: 18 | - name: image 19 | value: $(params.image) 20 | - name: rox_api_token 21 | value: roxsecrets 22 | - name: rox_central_endpoint 23 | value: roxsecrets 24 | - name: output_format 25 | value: json 26 | - name: image-check 27 | taskRef: 28 | name: rox-image-check 29 | kind: ClusterTask 30 | params: 31 | - name: image 32 | value: $(params.image) 33 | - name: rox_api_token 34 | value: roxsecrets 35 | - name: rox_central_endpoint 36 | value: roxsecrets 37 | -------------------------------------------------------------------------------- /ci/Tekton/Sample/rox-secrets.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | stringData: 3 | rox_central_endpoint: "{{ central_addr }}:{{ central_port }}" 4 | # The address:port tuple for StackRox Central (example - rox.stackrox.io:443) 5 | rox_api_token: "{{ rox_api_token }}" 6 | # StackRox API token with CI permissions 7 | # Refer to https://help.stackrox.com/docs/use-the-api/#generate-an-access-token 8 | kind: Secret 9 | metadata: 10 | name: roxsecrets 11 | namespace: pipeline-demo 12 | type: Opaque 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ci/Tekton/Tasks/rox-deployment-check-task.yml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: ClusterTask 3 | metadata: 4 | name: rox-deployment-check 5 | namespace: pipeline-demo 6 | spec: 7 | params: 8 | - name: rox_central_endpoint 9 | type: string 10 | description: Secret containing the address:port tuple for StackRox Central (example - rox.stackrox.io:443) 11 | - name: rox_api_token 12 | type: string 13 | description: Secret containing the StackRox API token with CI permissions 14 | - name: file 15 | type: string 16 | description: YAML file in the deployfiles workspace 17 | results: 18 | - name: check_output 19 | description: Output of `roxctl deployment check` 20 | workspaces: 21 | - name: deployfiles 22 | description: | 23 | The folder containing deployment files 24 | mountPath: /deployfile 25 | steps: 26 | - name: rox-deployment-check 27 | image: centos:8 28 | env: 29 | - name: ROX_API_TOKEN 30 | valueFrom: 31 | secretKeyRef: 32 | name: $(params.rox_api_token) 33 | key: rox_api_token 34 | - name: ROX_CENTRAL_ENDPOINT 35 | valueFrom: 36 | secretKeyRef: 37 | name: $(params.rox_central_endpoint) 38 | key: rox_central_endpoint 39 | script: | 40 | #!/usr/bin/env bash 41 | set +x 42 | cat /deployfile/deploy.yml 43 | curl -k -L -H "Authorization: Bearer $ROX_API_TOKEN" https://$ROX_CENTRAL_ENDPOINT/api/cli/download/roxctl-linux --output ./roxctl > /dev/null; echo "Getting roxctl" 44 | chmod +x ./roxctl > /dev/null 45 | ./roxctl deployment check --insecure-skip-tls-verify -e $ROX_CENTRAL_ENDPOINT -f /deployfile/$(params.file) -------------------------------------------------------------------------------- /ci/Tekton/Tasks/rox-image-check-task.yml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: ClusterTask 3 | metadata: 4 | name: rox-image-check 5 | namespace: pipeline-demo 6 | spec: 7 | params: 8 | - name: rox_central_endpoint 9 | type: string 10 | description: Secret containing the address:port tuple for StackRox Central (example - rox.stackrox.io:443) 11 | - name: rox_api_token 12 | type: string 13 | description: Secret containing the StackRox API token with CI permissions 14 | - name: image 15 | type: string 16 | description: Full name of image to scan (example -- gcr.io/rox/sample:5.0-rc1) 17 | results: 18 | - name: check_output 19 | description: Output of `roxctl image check` 20 | steps: 21 | - name: rox-image-check 22 | image: centos:8 23 | env: 24 | - name: ROX_API_TOKEN 25 | valueFrom: 26 | secretKeyRef: 27 | name: $(params.rox_api_token) 28 | key: rox_api_token 29 | - name: ROX_CENTRAL_ENDPOINT 30 | valueFrom: 31 | secretKeyRef: 32 | name: $(params.rox_central_endpoint) 33 | key: rox_central_endpoint 34 | script: | 35 | #!/usr/bin/env bash 36 | set +x 37 | curl -k -L -H "Authorization: Bearer $ROX_API_TOKEN" https://$ROX_CENTRAL_ENDPOINT/api/cli/download/roxctl-linux --output ./roxctl > /dev/null; echo "Getting roxctl" 38 | chmod +x ./roxctl > /dev/null 39 | ./roxctl image check --insecure-skip-tls-verify -e $ROX_CENTRAL_ENDPOINT --image $(params.image) -------------------------------------------------------------------------------- /ci/Tekton/Tasks/rox-image-scan-task.yml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: ClusterTask 3 | metadata: 4 | name: rox-image-scan 5 | namespace: pipeline-demo 6 | spec: 7 | params: 8 | - name: rox_central_endpoint 9 | type: string 10 | description: Secret containing the address:port tuple for StackRox Central (example - rox.stackrox.io:443) 11 | - name: rox_api_token 12 | type: string 13 | description: Secret containing the StackRox API token with CI permissions 14 | - name: image 15 | type: string 16 | description: Full name of image to scan (example -- gcr.io/rox/sample:5.0-rc1) 17 | - name: output_format 18 | type: string 19 | description: Output format (json | csv ) 20 | default: json 21 | steps: 22 | - name: rox-image-scan 23 | image: centos:8 24 | env: 25 | - name: ROX_API_TOKEN 26 | valueFrom: 27 | secretKeyRef: 28 | name: $(params.rox_api_token) 29 | key: rox_api_token 30 | - name: ROX_CENTRAL_ENDPOINT 31 | valueFrom: 32 | secretKeyRef: 33 | name: $(params.rox_central_endpoint) 34 | key: rox_central_endpoint 35 | script: | 36 | #!/usr/bin/env bash 37 | set +x 38 | export NO_COLOR="True" 39 | curl -k -L -H "Authorization: Bearer $ROX_API_TOKEN" https://$ROX_CENTRAL_ENDPOINT/api/cli/download/roxctl-linux --output ./roxctl > /dev/null; echo "Getting roxctl" 40 | chmod +x ./roxctl > /dev/null 41 | ./roxctl image scan --insecure-skip-tls-verify -e $ROX_CENTRAL_ENDPOINT --image $(params.image) --output $(params.output_format) 42 | -------------------------------------------------------------------------------- /ci/argo/README.md: -------------------------------------------------------------------------------- 1 | # Argo Workflow Example 2 | 3 | Provided is an Argo Workflow resource that will build, push, scan and check a container image. 4 | This workflow will also render a K8s deployment resource and check that against StackRox policies 5 | 6 | ### PreReqs: 7 | * Argo 8 | * K8s imagePullSecret named: `stackrox-io` 9 | * K8s secret: `rox-api` with the `data` key set to the StackRox API token 10 | * K8s secret: `regcred` with `config.json` container docker registry credentials (for pushing) 11 | 12 | ### Overrides: 13 | The workflow has the following values that can be overriden at submit time from the CLI: 14 | * `git-repo-path`: ex "https://github.com/logankimmel/hello-go.git" 15 | * `image-repo`: ex "docker.io/logankimmel/hello-go" 16 | 17 | ### Required Param: 18 | * `central`: Central hostname and port, ex: "central.rox.binbytes.io:443" 19 | * This must be declared on the command line: 20 | `argo submit argo.yml -n argo -v -p central="central.rox.binbytes.io:443"` 21 | 22 | LATEST TESTED: 3.0.50.0 -------------------------------------------------------------------------------- /ci/argo/argo.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Workflow 3 | metadata: 4 | generateName: hello-go- 5 | labels: 6 | workflows.argoproj.io/archive-strategy: "false" 7 | spec: 8 | entrypoint: cicd 9 | arguments: 10 | parameters: 11 | - name: git-repo-path 12 | value: https://github.com/logankimmel/hello-go.git 13 | - name: image-repo 14 | value: logankimmel/hello-go 15 | - name: central 16 | value: "{{workflow.parameters.central}}" 17 | templates: 18 | - name: cicd 19 | steps: 20 | - - name: checkout 21 | template: checkout 22 | arguments: 23 | parameters: 24 | - name: git-repo-path 25 | value: "{{workflow.parameters.git-repo-path}}" 26 | - - name: build-push-docker 27 | template: build-push-docker 28 | arguments: 29 | artifacts: 30 | - name: git-repo 31 | from: "{{steps.checkout.outputs.artifacts.source}}" 32 | parameters: 33 | - name: image-repo 34 | value: "{{workflow.parameters.image-repo}}" 35 | - name: image-tag 36 | value: "{{steps.checkout.outputs.parameters.tag}}" 37 | - - name: image-scan 38 | template: image-scan 39 | arguments: 40 | parameters: 41 | - name: image-repo 42 | value: "{{workflow.parameters.image-repo}}" 43 | - name: image-tag 44 | value: "{{steps.checkout.outputs.parameters.tag}}" 45 | - name: central 46 | value: "{{workflow.parameters.central}}" 47 | - - name: image-check 48 | template: image-check 49 | arguments: 50 | parameters: 51 | - name: image-tag 52 | value: "{{steps.checkout.outputs.parameters.tag}}" 53 | - name: image-repo 54 | value: "{{workflow.parameters.image-repo}}" 55 | - name: central 56 | value: "{{workflow.parameters.central}}" 57 | - - name: update-deployment 58 | template: update-deployment 59 | arguments: 60 | parameters: 61 | - name: image-tag 62 | value: "{{steps.checkout.outputs.parameters.tag}}" 63 | - name: image-repo 64 | value: "{{workflow.parameters.image-repo}}" 65 | artifacts: 66 | - name: git-repo 67 | from: "{{steps.checkout.outputs.artifacts.source}}" 68 | - - name: deployment-check 69 | template: deployment-check 70 | arguments: 71 | parameters: 72 | - name: central 73 | value: "{{workflow.parameters.central}}" 74 | artifacts: 75 | - name: deployment 76 | from: "{{steps.update-deployment.outputs.artifacts.deployment}}" 77 | - name: checkout 78 | inputs: 79 | parameters: 80 | - name: git-repo-path 81 | artifacts: 82 | - name: git-repo 83 | path: /src 84 | git: 85 | repo: "{{inputs.parameters.git-repo-path}}" 86 | metadata: 87 | labels: 88 | app: argo 89 | container: 90 | image: alpine/git 91 | resources: 92 | requests: 93 | cpu: 100m 94 | memory: 100Mi 95 | limits: 96 | cpu: 400m 97 | memory: 800Mi 98 | command: [sh, -c] 99 | args: ["cd /src && git rev-parse --short HEAD > /tmp/git-commit"] 100 | outputs: 101 | artifacts: 102 | - name: source 103 | path: /src 104 | parameters: 105 | - name: tag 106 | valueFrom: 107 | path: /tmp/git-commit 108 | - name: build-push-docker 109 | inputs: 110 | artifacts: 111 | - name: git-repo 112 | path: /src 113 | parameters: 114 | - name: image-tag 115 | - name: image-repo 116 | metadata: 117 | labels: 118 | app: argo 119 | container: 120 | image: gcr.io/kaniko-project/executor:debug 121 | workingDir: /src 122 | command: ["/kaniko/executor"] 123 | args: [ 124 | '--context', '/src', '--dockerfile', '/src/full.Dockerfile', '--destination', 125 | "{{inputs.parameters.image-repo}}:{{inputs.parameters.image-tag}}" 126 | ] 127 | volumeMounts: 128 | - name: regcred 129 | mountPath: /kaniko/.docker 130 | - name: image-scan 131 | inputs: 132 | parameters: 133 | - name: image-tag 134 | - name: image-repo 135 | - name: central 136 | metadata: 137 | labels: 138 | app: argo 139 | container: 140 | image: stackrox.io/roxctl:3.0.50.0 141 | volumeMounts: 142 | - name: api-token 143 | mountPath: /api-token 144 | command: ["/roxctl"] 145 | args: ["image", "scan", "--image", "{{inputs.parameters.image-repo}}:{{inputs.parameters.image-tag}}", "-e", "{{inputs.parameters.central}}", "--token-file", "/api-token/data"] 146 | 147 | - name: image-check 148 | inputs: 149 | parameters: 150 | - name: image-tag 151 | - name: image-repo 152 | - name: central 153 | metadata: 154 | labels: 155 | app: argo 156 | container: 157 | image: stackrox.io/roxctl:3.0.50.0 158 | volumeMounts: 159 | - name: api-token 160 | mountPath: /api-token 161 | command: ["/roxctl"] 162 | args: [ 163 | "image", "check", "--image", "{{inputs.parameters.image-repo}}:{{inputs.parameters.image-tag}}", 164 | "-e", "{{inputs.parameters.central}}", "--token-file", "/api-token/data", "--json" 165 | ] 166 | 167 | - name: update-deployment 168 | inputs: 169 | artifacts: 170 | - name: git-repo 171 | path: /src 172 | parameters: 173 | - name: image-tag 174 | - name: image-repo 175 | metadata: 176 | labels: 177 | app: argo 178 | container: 179 | image: alpine 180 | command: [/bin/sh, -c] 181 | args: ["sed s#%IMAGE%#{{inputs.parameters.image-repo}}:{{inputs.parameters.image-tag}}#g /src/deployment.yaml > /tmp/deployment.yaml"] 182 | outputs: 183 | artifacts: 184 | - name: deployment 185 | path: /tmp/deployment.yaml 186 | 187 | - name: deployment-check 188 | inputs: 189 | parameters: 190 | - name: central 191 | artifacts: 192 | - name: deployment 193 | path: /tmp/deployment.yaml 194 | metadata: 195 | labels: 196 | app: argo 197 | container: 198 | image: stackrox.io/roxctl:3.0.50.0 199 | volumeMounts: 200 | - name: api-token 201 | mountPath: /api-token 202 | command: ["/roxctl"] 203 | args: ["deployment", "check", "--file", "/tmp/deployment.yaml", "-e", "{{inputs.parameters.central}}", "--token-file", "/api-token/data"] 204 | 205 | imagePullSecrets: 206 | - name: stackrox-io 207 | ttlStrategy: 208 | secondsAfterCompletion: 3600 209 | volumes: 210 | - name: regcred 211 | secret: 212 | secretName: regcred 213 | - name: api-token 214 | secret: 215 | secretName: rox-api 216 | 217 | 218 | -------------------------------------------------------------------------------- /ci/function/Google Function/CI integration sample/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # StackRox Google Function scan sample 2 | # author neil@stackrox.com 3 | 4 | version: 2.1 5 | jobs: 6 | scan: 7 | docker: 8 | - image: cimg/base:2020.10 9 | steps: 10 | - run: | 11 | ROX_RETURN=$(curl -L -H "Authorization: Bearer $GOOG_API_TOKEN" https://<>.cloudfunctions.net/roxctl_image_check \ 12 | --silent --fail --show-error \ 13 | --header "Content-Type: application/json" \ 14 | --request POST \ 15 | --data "{\"rox_central_endpoint\": \"$ROX_CENTRAL_ENDPOINT\",\"rox_api_token\": \"$ROX_API_TOKEN\",\"rox_image\": \"mysql:5.6.30\"}") 16 | echo $ROX_RETURN | jq --raw-output '.output' 17 | if [ "$(echo $ROX_RETURN | jq --raw-output '.build')" = "fail" ]; then 18 | exit 1 19 | fi 20 | 21 | # This uses cURL to execute the Google Cloud Function. The function returns JSON with pass/fail and the output. 22 | # We echo the output and, finally, if the result is fail, exit with a non-zero return code to fail the build 23 | 24 | # Replace https://<> with the appropriate location of the Google Cloud Function. 25 | 26 | # Parameters for the StackRox Central and authentication to the Google Cloud Function are configured as environment variables 27 | # in CircleCI. 28 | # 29 | # `GOOG_API_TOKEN`: A Bearer token for access to the roxctl_image_check function 30 | # `ROX_CENTRAL_ENDPOINT`: the hostname/IP and port for Central (`central.stackrox.com:443`) 31 | # `ROX_API_TOKEN`: an API token from Central with at least CI privileges 32 | 33 | workflows: 34 | scanimage: 35 | when: always 36 | jobs: 37 | - scan: 38 | name: scan -------------------------------------------------------------------------------- /ci/function/Google Function/README.md: -------------------------------------------------------------------------------- 1 | # Google Function CI Scan Sample 2 | 3 | 4 | This sample uses a Google Cloud Function to scan an image and return results. This is useful for scanning builds in hosted CI solutions when Central is not accessible from the Internet -- the function can be used as a proxy. 5 | 6 | The `roxctl_image_check` directory has the sample Python function. This script takes an HTTP POST with a JSON body containing the parameters and returns a pass/fail and the policy check output. 7 | 8 | Args: 9 | JSON array with 10 | `rox_central_endpoint` -- the hostname/IP and port for Central (`central.stackrox.com:443`) 11 | `rox_api_token` -- an API token from Central with at least CI privileges 12 | `rox_image` -- the image to scan (`registry.stackrox.local/frontend:1.5.1`) 13 | Returns: 14 | JSON array with 15 | `build` -- either `pass` or `fail` to indicate if any policies are enforced 16 | `output` -- the output of `roxctl image check` 17 | 18 | The `CI Integration sample` directory contains a sample CI integration for consuming this function in a CircleCI build pipeline. 19 | 20 | OWNER: neilcar 21 | 22 | LATEST TESTED: 3.0.52.0 -------------------------------------------------------------------------------- /ci/function/Google Function/roxctl_image_check/main.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import os 4 | import stat 5 | import subprocess 6 | 7 | def image_check(request): 8 | """Checks an image with roxctl 9 | Args: 10 | JSON array with 11 | `rox_central_endpoint` -- the hostname/IP and port for Central (`central.stackrox.com:443`) 12 | `rox_api_token` -- an API token from Central with at least CI privileges 13 | `rox_image` -- the image to scan (`registry.stackrox.local/frontend:1.5.1`) 14 | Returns: 15 | JSON array with 16 | `build` -- either `pass` or `fail` to indicate if any policies are enforced 17 | `output` -- the output of `roxctl image check` 18 | """ 19 | request_json = request.get_json() 20 | 21 | rox_central_endpoint = request_json['rox_central_endpoint'] 22 | rox_api_token = request_json['rox_api_token'] 23 | rox_image = request_json['rox_image'] 24 | 25 | print("Using roxctl to scan image: " + rox_image) 26 | 27 | download_roxctl(rox_central_endpoint, rox_api_token) 28 | 29 | out,err = roxctl_image_check(rox_central_endpoint, rox_api_token, rox_image) 30 | 31 | if err == 0: 32 | err_text = "pass" 33 | else: 34 | err_text = "fail" 35 | 36 | json_return = {"build": err_text, "output": out.decode("utf-8")} 37 | 38 | return json.dumps(json_return) 39 | 40 | def download_roxctl(rox_central_endpoint, rox_api_token): 41 | url = "https://" + rox_central_endpoint + "/api/cli/download/roxctl-linux" 42 | token = "Bearer " + rox_api_token 43 | print("token: " + token) 44 | headers = {'Authorization': token} 45 | r = requests.get(url, headers=headers) 46 | open('/tmp/roxctl', 'wb').write(r.content) 47 | st = os.stat('/tmp/roxctl') 48 | os.chmod('/tmp/roxctl', st.st_mode | stat.S_IEXEC) 49 | 50 | 51 | def roxctl_image_check(rox_central_endpoint, rox_api_token, rox_image): 52 | os.environ["ROX_API_TOKEN"] = rox_api_token 53 | roxctl = subprocess.Popen(['/tmp/roxctl', 'image', 'check', '-e', rox_central_endpoint, '--image', rox_image], 54 | stdout=subprocess.PIPE, 55 | stderr=subprocess.STDOUT) 56 | out, err = roxctl.communicate() 57 | return out, roxctl.returncode -------------------------------------------------------------------------------- /ci/function/Google Function/roxctl_image_check/requirements.txt: -------------------------------------------------------------------------------- 1 | # Function dependencies, for example: 2 | # package>=version 3 | 4 | requests 5 | -------------------------------------------------------------------------------- /completions/fish/roxctl.fish: -------------------------------------------------------------------------------- 1 | # Command Sets 2 | set -l base_commands central cluster collector deployment helm help image scanner sensor version 3 | set -l central_commands backup cert db debug generate init-bundles license userpki whoami 4 | set -l image_commands check scan 5 | set -l deployment_commands check 6 | set -l scanner_commands generate upload-db 7 | set -l sensor_commands generate generate-certs get-bundle 8 | 9 | # Disable file completions 10 | complete -c roxctl -f 11 | 12 | # Base flags 13 | complete -c roxctl -s h -l help -d "more information about a command" 14 | complete -c roxctl -s e -l endpoint -d "endpoint for service to contact" 15 | complete -c roxctl -l insecure -d "skip tls certification validation" 16 | complete -c roxctl -s p -l password -d "password for basic auth" 17 | complete -c roxctl -s e -l endpoint -d "stackrox central endpoint" 18 | complete -c roxctl -l token-file -d "use API token in the provided file" 19 | 20 | 21 | # Base commands 22 | complete -c roxctl -n "not __fish_seen_subcommand_from $base_commands" -a "$base_commands" 23 | 24 | # Central subcommands 25 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "backup" -d "backup the StackRox database and certificates" 26 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "cert" -d "download certificate chain for the Central service" 27 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "db" -d "commands related to the StackRox database" 28 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "debug" -d "debug the Central Service" 29 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "generate" -d "generate k8s manifests to deploy StackRox Central" 30 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "init-bundles" -d "manage cluster init bundle" 31 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "license" -d "add and display license information" 32 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "userpki" -d "manage user certificate authorization providers" 33 | complete -c roxctl -n "__fish_seen_subcommand_from central; and not __fish_seen_subcommand_from $central_commands" -a "whoami" -d "info about the current user" 34 | 35 | # Image flags 36 | complete -c roxctl -n "__fish_seen_subcommand_from $image_commands" -s t -l timeout -d "timeout for api requests (default 10m0s)" 37 | complete -c roxctl -n "__fish_seen_subcommand_from $image_commands" -s i -l image -d "image name and tag" -a "(docker image list --format '{{.Repository}}:{{.Tag}}')" 38 | complete -c roxctl -n "__fish_seen_subcommand_from $image_commands" -s a -l include-snoozed -d "return both snoozed and unsnoozed CVEs if set to false" 39 | 40 | # Image subcommands 41 | complete -c roxctl -n "__fish_seen_subcommand_from image; and not __fish_seen_subcommand_from $image_commands" -a "check" -d "check images for build time policy violations" 42 | complete -c roxctl -n "__fish_seen_subcommand_from image; and not __fish_seen_subcommand_from $image_commands" -a "scan" -d "scan the specified images" 43 | 44 | # Deployment flags 45 | complete -c roxctl -n "__fish_seen_subcommand_from $deployment_commands" -s t -l timeout -d "timeout for api requests (default 10m0s)" 46 | complete -c roxctl -n "__fish_seen_subcommand_from $deployment_commands" -s f -l file -d "evaluate policies against yaml file" -a "(find . | grep -i -e '\.y\(aml\|ml\)' | head -n 10 | sort -d -u)" 47 | 48 | 49 | # Deployment subcommands 50 | complete -c roxctl -n "__fish_seen_subcommand_from deployment; and not __fish_seen_subcommand_from $deployment_commands" -a "check" -d "check deployments for deploy time policy violations" 51 | 52 | # Scanner flags 53 | complete -c roxctl -n "__fish_seen_subcommand_from $scanner_commands" -s t -l timeout -d "timeout for api requests (default 10m0s)" 54 | 55 | # Scanner subcommands 56 | complete -c roxctl -n "__fish_seen_subcommand_from scanner; and not __fish_seen_subcommand_from $scanner_commands" -a "generate" -d "generate k8s manifests to deploy StackRox Scanner" 57 | complete -c roxctl -n "__fish_seen_subcommand_from scanner; and not __fish_seen_subcommand_from $scanner_commands" -a "upload-db" -d "upload a vulnerability database for the StackRox Scanner" 58 | 59 | # Sensor flags 60 | complete -c roxctl -n "__fish_seen_subcommand_from $sensor_commands" -s t -l timeout -d "timeout for api requests (default 10m0s)" 61 | 62 | # Sensor subcommands 63 | complete -c roxctl -n "__fish_seen_subcommand_from sensor; and not __fish_seen_subcommand_from $sensor_commands" -a "generate" -d "generate k8s manifests to deploy StackRox services into secured clusters" 64 | complete -c roxctl -n "__fish_seen_subcommand_from sensor; and not __fish_seen_subcommand_from $sensor_commands" -a "generate-certs" -d "download a YAML file with renewed certificates" 65 | complete -c roxctl -n "__fish_seen_subcommand_from sensor; and not __fish_seen_subcommand_from $sensor_commands" -a "get-bundle" -d "download a bundle with the files to deploy StackRox services into a cluster" 66 | 67 | 68 | -------------------------------------------------------------------------------- /compliance/scan-compliance.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [[ -z $ROX_API_TOKEN ]]; then 6 | echo "ROX_API_TOKEN needs to be set" 1>&2 7 | exit 1 8 | fi 9 | 10 | if [[ -z "$ROX_ENDPOINT" ]]; then 11 | echo "ROX_ENDPOINT needs to be set" 1>&2 12 | exit 1 13 | fi 14 | 15 | if [ ! -x "$(which jq)" ]; then 16 | echo "jq is a required for this script to work correctly" 1>&2 17 | exit 1 18 | fi 19 | 20 | function roxcurl() { 21 | curl -sk -H "Authorization: Bearer $ROX_API_TOKEN" "$@" 22 | } 23 | 24 | cluster_ids=$(roxcurl "$ROX_ENDPOINT/v1/clusters" | jq -r .clusters[].id) 25 | for cluster in $cluster_ids; do 26 | echo "Triggering compliance run for cluster $cluster" 27 | runs=$(roxcurl "$ROX_ENDPOINT/v1/compliancemanagement/runs" -X POST -d '{ "selection": { "cluster_id": "'"$cluster"'", "standard_id": "*" } }') 28 | run_ids=$(jq -r .startedRuns[].id <<< "$runs") 29 | num_runs=$(jq '.startedRuns | length' <<< "$runs") 30 | while true; do 31 | size="$num_runs" 32 | for run_id in $run_ids; do 33 | run_status=$(roxcurl "$ROX_ENDPOINT/v1/compliancemanagement/runstatuses" --data-urlencode "run_ids=$run_id" | jq -r .runs[0]) 34 | run_state=$(jq -r .state <<< "$run_status") 35 | standard=$(jq -r .standardId <<< "$run_status") 36 | echo "Run $run_id for cluster $cluster and standard $standard has state $run_state" 37 | if [[ "$run_state" == "FINISHED" ]]; then 38 | size=$(( size - 1)) 39 | fi 40 | done 41 | if [[ "$size" == 0 ]]; then 42 | echo "Compliance for cluster $cluster has completed" 43 | break 44 | fi 45 | sleep 5 46 | done 47 | done 48 | -------------------------------------------------------------------------------- /guides/cloud-provider-integrations/azure-service-principal-m2m-auth.md: -------------------------------------------------------------------------------- 1 | ## Using Azure Entra ID service principals for machine to machine auth with ACS 2 | 3 | **Note:** Instructions provided in this guide are provided as-is without warranty or support from Red Hat. 4 | 5 | ### 1. Create Azure service principal 6 | 7 | For this, we can use [the following guide from Microsoft Learn](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal?source=recommendations#register-an-application-with-microsoft-entra-id-and-create-a-service-principal). 8 | 9 | The only step: “**Register an application with Microsoft Entra ID and create a service principal”** is required. We do not have to add roles for that service principal because it does not have to access any Azure resource. It will be used only for authentication in ACS. 10 | 11 | ### 2. Setup authentication for created service principal 12 | 13 | This is required in order for the service principal to authenticate to Azure. 14 | 15 | We can use [the following steps from the same Microsoft Learn page](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal?source=recommendations#set-up-authentication). 16 | 17 | After authentication setup, we can use the `az` command to log into Azure and retrieve the access token required to do m2m authentication to ACS. 18 | 19 | ### 3. Login with `az` 20 | 21 | This example uses a secret to authenticate (**Option 3** in the “Setup authentication” guide mentioned under step 2.). 22 | 23 | ``` 24 | az login --service-principal \ 25 | --username \ 26 | --password \ 27 | --tenant \ 28 | --allow-no-subscriptions 29 | ``` 30 | 31 | It is important to use the `--allow-no-subscriptions` flag if the service principal does not have any roles. 32 | 33 | **Note:** Logging as a regular user with `az login` would also work. In that case, the difference would be that we need to use `unique_name` or another claim from the token during the configuration of ACS machine access (Step 4\. below) 34 | 35 | After this, the command: 36 | 37 | ``` 38 | az account list --output yamlc 39 | ``` 40 | 41 | Should output account with `user` property. The name of that user should be the service principal ID. 42 | 43 | ``` 44 | user: 45 | name: 46 | type: servicePrincipal 47 | ``` 48 | 49 | ### 4. Configure ACS 50 | 51 | You can follow [Configuring short-lived access documentation on Red Hat documentation](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_security_for_kubernetes/4.6/html/operating/managing-user-access#configure-short-lived-access). *Ensure to use documentation from used ACS version.* 52 | 53 | Create a **Machine access configuration** - with the following fields: 54 | 55 | Issuer: `https://sts.windows.net//` 56 | 57 | Add a rule with: 58 | Key: `appid` 59 | Value: `` 60 | 61 | **Important:** ACS has to be able to access: `https://sts.windows.net//.well-known/openid-configuration` 62 | 63 | ### 5. Test everything 64 | 65 | Use the following `roxctl` command: 66 | 67 | ``` 68 | roxctl central machine-to-machine exchange \ 69 | --token="$(az account get-access-token --tenant "" --query "accessToken" --output tsv)" 70 | ``` 71 | 72 | *If `--output tsv` does not provide valid token format. There is option to use JSON output and `jq` command to select token from payload.* 73 | 74 | After successful login, running: `roxctl central whoami` should output ACS authentication information. And “User name:” in the output should be the same as provided `` in the `az` login command. 75 | -------------------------------------------------------------------------------- /ingress/contour/central-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: projectcontour.io/v1 2 | kind: HTTPProxy 3 | metadata: 4 | name: central 5 | namespace: stackrox 6 | spec: 7 | tcpproxy: 8 | services: 9 | - name: central 10 | port: 443 11 | virtualhost: 12 | fqdn: central.blah.blar.tld 13 | tls: 14 | passthrough: true 15 | -------------------------------------------------------------------------------- /ingress/haproxy/Readme.md: -------------------------------------------------------------------------------- 1 | # HAProxy Ingress configuration for StackRox Central 2 | 3 | This configuration provides a simple HAProxy ssl-passthrough ingress for StackRox Central. 4 | 5 | ## Files 6 | * `central-hap-ingress.yaml` The central-ingress definition for the stackrox namespace 7 | * `haproxy-controller.yaml` Example HAProxy controller configuration with ssl-passthrough enabled. If you're running HAProxy controller already, you'll probably only need the lines from the ConfigMap section to enable ssl-passthrough. 8 | -------------------------------------------------------------------------------- /ingress/haproxy/central-hap-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: central-ingress 5 | namespace: stackrox 6 | annotations: 7 | ingress.kubernetes.io/ssl-passthrough: "true" 8 | spec: 9 | tls: 10 | - hosts: 11 | - central.example.com 12 | rules: 13 | - host: central.example.com 14 | http: 15 | paths: 16 | - backend: 17 | serviceName: central 18 | servicePort: 443 19 | -------------------------------------------------------------------------------- /ingress/haproxy/haproxy-controller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: haproxy-controller 6 | 7 | --- 8 | apiVersion: v1 9 | kind: ServiceAccount 10 | metadata: 11 | name: haproxy-ingress-service-account 12 | namespace: haproxy-controller 13 | 14 | --- 15 | kind: ClusterRole 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | metadata: 18 | name: haproxy-ingress-cluster-role 19 | rules: 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - configmaps 24 | - endpoints 25 | - nodes 26 | - pods 27 | - services 28 | - namespaces 29 | - events 30 | - serviceaccounts 31 | verbs: 32 | - get 33 | - list 34 | - watch 35 | - apiGroups: 36 | - "extensions" 37 | resources: 38 | - ingresses 39 | - ingresses/status 40 | verbs: 41 | - get 42 | - list 43 | - watch 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - secrets 48 | verbs: 49 | - get 50 | - list 51 | - watch 52 | - create 53 | - patch 54 | - update 55 | - apiGroups: 56 | - extensions 57 | resources: 58 | - ingresses 59 | verbs: 60 | - get 61 | - list 62 | - watch 63 | 64 | --- 65 | kind: ClusterRoleBinding 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | metadata: 68 | name: haproxy-ingress-cluster-role-binding 69 | namespace: haproxy-controller 70 | roleRef: 71 | apiGroup: rbac.authorization.k8s.io 72 | kind: ClusterRole 73 | name: haproxy-ingress-cluster-role 74 | subjects: 75 | - kind: ServiceAccount 76 | name: haproxy-ingress-service-account 77 | namespace: haproxy-controller 78 | 79 | --- 80 | apiVersion: v1 81 | kind: ConfigMap 82 | metadata: 83 | name: haproxy 84 | namespace: haproxy-controller 85 | data: 86 | ssl-passthrough: "true" 87 | 88 | --- 89 | apiVersion: apps/v1 90 | kind: Deployment 91 | metadata: 92 | labels: 93 | run: ingress-default-backend 94 | name: ingress-default-backend 95 | namespace: haproxy-controller 96 | spec: 97 | replicas: 1 98 | selector: 99 | matchLabels: 100 | run: ingress-default-backend 101 | template: 102 | metadata: 103 | labels: 104 | run: ingress-default-backend 105 | spec: 106 | containers: 107 | - name: ingress-default-backend 108 | image: gcr.io/google_containers/defaultbackend:1.0 109 | ports: 110 | - containerPort: 8080 111 | 112 | --- 113 | apiVersion: v1 114 | kind: Service 115 | metadata: 116 | labels: 117 | run: ingress-default-backend 118 | name: ingress-default-backend 119 | namespace: haproxy-controller 120 | spec: 121 | selector: 122 | run: ingress-default-backend 123 | ports: 124 | - name: port-1 125 | port: 8080 126 | protocol: TCP 127 | targetPort: 8080 128 | 129 | --- 130 | apiVersion: apps/v1 131 | kind: Deployment 132 | metadata: 133 | labels: 134 | run: haproxy-ingress 135 | name: haproxy-ingress 136 | namespace: haproxy-controller 137 | spec: 138 | replicas: 1 139 | selector: 140 | matchLabels: 141 | run: haproxy-ingress 142 | template: 143 | metadata: 144 | labels: 145 | run: haproxy-ingress 146 | spec: 147 | serviceAccountName: haproxy-ingress-service-account 148 | containers: 149 | - name: haproxy-ingress 150 | image: haproxytech/kubernetes-ingress 151 | args: 152 | - --configmap=haproxy-controller/haproxy 153 | - --default-backend-service=haproxy-controller/ingress-default-backend 154 | resources: 155 | requests: 156 | cpu: "500m" 157 | memory: "50Mi" 158 | livenessProbe: 159 | httpGet: 160 | path: /healthz 161 | port: 1042 162 | ports: 163 | - name: http 164 | containerPort: 80 165 | - name: https 166 | containerPort: 443 167 | - name: stat 168 | containerPort: 1024 169 | env: 170 | - name: TZ 171 | value: "Etc/UTC" 172 | - name: POD_NAME 173 | valueFrom: 174 | fieldRef: 175 | fieldPath: metadata.name 176 | - name: POD_NAMESPACE 177 | valueFrom: 178 | fieldRef: 179 | fieldPath: metadata.namespace 180 | 181 | --- 182 | apiVersion: v1 183 | kind: Service 184 | metadata: 185 | labels: 186 | run: haproxy-ingress 187 | name: haproxy-ingress 188 | namespace: haproxy-controller 189 | spec: 190 | selector: 191 | run: haproxy-ingress 192 | type: LoadBalancer 193 | ports: 194 | - name: http 195 | port: 80 196 | protocol: TCP 197 | targetPort: 80 198 | - name: https 199 | port: 443 200 | protocol: TCP 201 | targetPort: 443 202 | - name: stat 203 | port: 1024 204 | protocol: TCP 205 | targetPort: 1024 206 | -------------------------------------------------------------------------------- /ingress/istio-gw/Readme.md: -------------------------------------------------------------------------------- 1 | # Istio Gateway Ingress configuration for StackRox Central 2 | 3 | This configuration provides a simple Istio Gateway tls-passthrough ingress for StackRox Central. Tested on Istio v1.8. 4 | 5 | ## Notes 6 | * By default, Istio's Ingress Gateway creates a loadbalancer service. You can see the assigned IP or hostname using `kubectl -n istio-system get svc` 7 | * Routing to appropriate backend depends on DNS. This YAML uses `central.example.com` which should be customized for your environment 8 | 9 | ## Files 10 | * `central-istio-gw-passthrough.yaml` The Gateway, VirtualService, and DestinationRule definitions for the stackrox namespace 11 | 12 | OWNER: srcporter 13 | 14 | LAST TESTED VERSION: 3.0.52.1 15 | -------------------------------------------------------------------------------- /ingress/istio-gw/central-istio-gw-passthrough.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: DestinationRule 3 | metadata: 4 | name: central 5 | namespace: stackrox 6 | spec: 7 | host: central.stackrox 8 | trafficPolicy: 9 | loadBalancer: 10 | simple: LEAST_CONN 11 | --- 12 | apiVersion: networking.istio.io/v1alpha3 13 | kind: Gateway 14 | metadata: 15 | name: central-gateway 16 | namespace: stackrox 17 | spec: 18 | selector: 19 | istio: ingressgateway # use Istio default gateway implementation 20 | servers: 21 | - port: 22 | number: 443 23 | name: https 24 | protocol: TLS 25 | tls: 26 | mode: PASSTHROUGH 27 | hosts: 28 | - "central.example.com" 29 | --- 30 | apiVersion: networking.istio.io/v1alpha3 31 | kind: VirtualService 32 | metadata: 33 | name: central 34 | namespace: stackrox 35 | spec: 36 | hosts: 37 | - central.example.com 38 | gateways: 39 | - central-gateway 40 | tls: 41 | - match: 42 | - port: 443 43 | sniHosts: 44 | - central.example.com 45 | route: 46 | - destination: 47 | host: central 48 | port: 49 | number: 443 50 | 51 | -------------------------------------------------------------------------------- /ingress/nginx/Readme.md: -------------------------------------------------------------------------------- 1 | # Nginx Ingress configuration for StackRox Central 2 | 3 | This configuration provides two examples for Kubernetes Nginx Ingress 4 | 5 | Note that these apply to the open-source [Kubernetes nginx ingress](https://kubernetes.github.io/ingress-nginx/) and **not** the nginx plus family of products. 6 | 7 | ## Files 8 | * `central-nginx-passthrough-ingress.yaml` Central-ingress definition for the stackrox namespace that uses ssl-passthrough. 9 | * `central-nginx-encrypt-ingress.yaml` Central-ingress that terminates TLS at nginx, and re-encrypts to the Central service. Customization of hostnames and certificate secrets is recommended. 10 | 11 | 12 | NOTE: in order to use the ssl-passthrough annotation and have it work correctly, the nginx ingress controller must be deployed with the `--enable-ssl-passthrough` option. 13 | 14 | Please review the [documentation for TLS/HTTPS](https://kubernetes.github.io/ingress-nginx/user-guide/tls/#ssl-passthrough) on nginx ingress. 15 | 16 | In a standard installation of the ingress controller you can add this to a running pod with: 17 | 18 | `kubectl edit nginx-ingress-controller -n ingress-nginx` 19 | 20 | Change the containers spec for the controller to add the --enable-ssl-passthrough argument: 21 | 22 | spec: 23 | containers: 24 | - args: 25 | - /nginx-ingress-controller 26 | - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller 27 | - --election-id=ingress-controller-leader 28 | - --ingress-class=nginx 29 | - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller 30 | - --validating-webhook=:8443 31 | - --validating-webhook-certificate=/usr/local/certificates/cert 32 | - --validating-webhook-key=/usr/local/certificates/key 33 | - --enable-ssl-passthrough 34 | 35 | OWNER: srcporter 36 | LAST TESTED VERSION: 3.0.54.0 37 | -------------------------------------------------------------------------------- /ingress/nginx/central-nginx-encrypt-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | annotations: 5 | kubernetes.io/ingress.class: "nginx" 6 | nginx.ingress.kubernetes.io/backend-protocol: "GRPCS" 7 | nginx.ingress.kubernetes.io/proxy-body-size: "0" 8 | name: central-ingress 9 | namespace: stackrox 10 | spec: 11 | rules: 12 | - host: central.example.com 13 | http: 14 | paths: 15 | - backend: 16 | serviceName: central 17 | servicePort: 443 18 | tls: 19 | - secretName: central-default-tls-cert 20 | hosts: 21 | - central.example.com 22 | 23 | 24 | -------------------------------------------------------------------------------- /ingress/nginx/central-nginx-passthrough-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: central-ingress 5 | annotations: 6 | nginx.ingress.kubernetes.io/ssl-passthrough: "true" 7 | nginx.ingress.kubernetes.io/backend-protocol: "GRPCS" 8 | namespace: stackrox 9 | spec: 10 | rules: 11 | - http: 12 | paths: 13 | - path: / 14 | backend: 15 | serviceName: central 16 | servicePort: 443 17 | -------------------------------------------------------------------------------- /ingress/traefik/Readme.md: -------------------------------------------------------------------------------- 1 | # Traefik ingress configuration for StackRox Central 2 | 3 | This configuration provides a Traefik ingress definition for StackRox central which terminates TLS at the Ingress and connects to Central over TLS 4 | 5 | ## Traefik config 6 | * Strongly recommended that Traefik be configured to accept TLS connections at the *frontend*. In the ingress example here, the TLS certificate presented by Traefik is in the secret `stackrox-central-tls`. This certificate can be self-signed or publicly signed, but must be compatible with the requirements that Traefik documents at https://doc.traefik.io/traefik/v1.7/user-guide/kubernetes/#add-a-tls-certificate-to-the-ingress 7 | * In order to make a TLS connection to StackRox Central on the *backend*, the certificate used by central must either be signed by a trusted root (recommended), or Traefik must be configured with the --insecureskipverify option. 8 | * Traefik configurations with TLS router can avoid the certificate configuration with the passthrough option https://doc.traefik.io/traefik/routing/routers/#passthrough 9 | 10 | ## Notes 11 | * Note that if you use a self-signed certificate for the any Ingress frontend, StackRox Sensors in remote clusters must be configured to trust that certificate https://help.stackrox.com/docs/configure-stackrox/configure-custom-certificates/#configure-sensor-to-trust-custom-certificates 12 | * Routing to appropriate backend depends on DNS. This YAML uses `central.example.com` which should be customized for your environment 13 | 14 | ## Files 15 | * `central-traefik-ingress.yaml` Central Ingress definition for Traefik 16 | 17 | OWNER: srcporter 18 | 19 | LAST TESTED VERSION: 3.0.53.0 20 | -------------------------------------------------------------------------------- /ingress/traefik/central-traefik-ingress.yaml: -------------------------------------------------------------------------------- 1 | kind: Ingress 2 | apiVersion: extensions/v1beta1 3 | metadata: 4 | name: central-ingress 5 | namespace: stackrox 6 | 7 | spec: 8 | rules: 9 | - host: central.example.com 10 | http: 11 | paths: 12 | - path: / 13 | backend: 14 | serviceName: central 15 | servicePort: 443 16 | tls: 17 | - secretName: stackrox-central-tls 18 | 19 | -------------------------------------------------------------------------------- /policies/CVE-2021-4034-build-deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": [ 3 | { 4 | "name": "PwnKit: CVE-2021-4034 - Polkit local privilege escalation vulnerability", 5 | "description": "Alert on deployments with images containing the PwnKit vulnerability (CVE-2021-4034). This is a local privilege escalation vulnerability impacting the pkexec binary.", 6 | "rationale": "This vulnerability allows an unprivileged local attacker to escalate privileges, bypassing any authentication and policies due to incorrect handling of the process argument vector.", 7 | "remediation": "Update the polkit package to 0.112-26.el7_9.1 (for RHEL 7-based container images) or polkit-0.115-13.el8_5.1 (for RHEL 8-based container images).", 8 | "disabled": false, 9 | "categories": [ 10 | "Vulnerability Management" 11 | ], 12 | "fields": null, 13 | "lifecycleStages": [ 14 | "BUILD", 15 | "DEPLOY" 16 | ], 17 | "eventSource": "NOT_APPLICABLE", 18 | "whitelists": [], 19 | "exclusions": [], 20 | "scope": [], 21 | "severity": "CRITICAL_SEVERITY", 22 | "enforcementActions": [], 23 | "notifiers": [], 24 | "lastUpdated": null, 25 | "SORTName": "", 26 | "SORTLifecycleStage": "", 27 | "SORTEnforcement": false, 28 | "policyVersion": "1.1", 29 | "policySections": [ 30 | { 31 | "sectionName": "", 32 | "policyGroups": [ 33 | { 34 | "fieldName": "CVE", 35 | "booleanOperator": "OR", 36 | "negate": false, 37 | "values": [ 38 | { 39 | "value": "CVE-2021-4034" 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | ], 46 | "mitreAttackVectors": [], 47 | "criteriaLocked": true, 48 | "mitreVectorsLocked": true, 49 | "isDefault": false 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /policies/CVE-2021-44228-build-deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": [ 3 | { 4 | "name": "Log4Shell: CVE-2021-44228 - log4j Remote Code Execution vulnerability", 5 | "description": "Alert on deployments with images containing the Log4Shell vulnerability (CVE-2021-44228). This is a flaw in the Java logging library Apache Log4j in versions from 2.0.0 and before as well as version 2.14.1.", 6 | "rationale": "This vulnerability allows a remote attacker to execute code on the server if the system logs an attacker-controlled string value with the attacker's JNDI LDAP server lookup.", 7 | "remediation": "Update the log4j libary to version 2.15.0 (which disables the feature by default), 2.16.0 (which removes the functionality) or later. If not possible to upgrade, there are two possible mitigations for this flaw in versions from 2.10 to 2.14.1: Set the system property log4j2.formatMsgNoLookups to true, or remove the JndiLookup class from the classpath.", 8 | "disabled": false, 9 | "categories": [ 10 | "Vulnerability Management" 11 | ], 12 | "fields": null, 13 | "lifecycleStages": [ 14 | "BUILD", 15 | "DEPLOY" 16 | ], 17 | "eventSource": "NOT_APPLICABLE", 18 | "whitelists": [], 19 | "exclusions": [], 20 | "scope": [], 21 | "severity": "CRITICAL_SEVERITY", 22 | "enforcementActions": [], 23 | "notifiers": [], 24 | "lastUpdated": null, 25 | "SORTName": "", 26 | "SORTLifecycleStage": "", 27 | "SORTEnforcement": false, 28 | "policyVersion": "1.1", 29 | "policySections": [ 30 | { 31 | "sectionName": "", 32 | "policyGroups": [ 33 | { 34 | "fieldName": "CVE", 35 | "booleanOperator": "OR", 36 | "negate": false, 37 | "values": [ 38 | { 39 | "value": "CVE-2021-44228" 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | ], 46 | "mitreAttackVectors": [], 47 | "criteriaLocked": true, 48 | "mitreVectorsLocked": true, 49 | "isDefault": true 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /policies/leaky-vessels.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": [ 3 | { 4 | "name": "Leaky Vessels: runc container breakout", 5 | "description": "CVE-2024-21626 is a vulnerability in the runc container runtime allowing an attacker to break out of the container isolation and achieve full root RCE via a crafted image that exploits an issue within the WORKDIR instruction's handling.", 6 | "rationale": "This vulnerability potentially allows an attacker to access host resources using a crafted file descriptor from the container, impacting both build and deployment operations.", 7 | "remediation": "Remove Containerfile lines resembling 'WORKDIR /proc/self/fd/[ID]' (with ID being a system dependent file descriptor)", 8 | "disabled": false, 9 | "categories": [ 10 | "Anomalous Activity", 11 | "Kubernetes" 12 | ], 13 | "lifecycleStages": [ 14 | "BUILD", 15 | "DEPLOY" 16 | ], 17 | "eventSource": "NOT_APPLICABLE", 18 | "exclusions": [], 19 | "scope": [], 20 | "severity": "HIGH_SEVERITY", 21 | "enforcementActions": [], 22 | "notifiers": [], 23 | "SORTName": "", 24 | "SORTLifecycleStage": "", 25 | "SORTEnforcement": false, 26 | "policyVersion": "1.1", 27 | "policySections": [ 28 | { 29 | "sectionName": "Policy Section 1", 30 | "policyGroups": [ 31 | { 32 | "fieldName": "Dockerfile Line", 33 | "booleanOperator": "OR", 34 | "negate": false, 35 | "values": [ 36 | { 37 | "value": "WORKDIR=.*\\/proc\\/self\\/fd\\/.*" 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | ], 44 | "mitreAttackVectors": [ 45 | { 46 | "tactic": "TA0004", 47 | "techniques": [ 48 | "T1611" 49 | ] 50 | } 51 | ], 52 | "criteriaLocked": false, 53 | "mitreVectorsLocked": false, 54 | "isDefault": false 55 | } 56 | ] 57 | } -------------------------------------------------------------------------------- /policies/oc-debug-runtime.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": [ 3 | { 4 | "name": "Possible 'oc debug' access to pod", 5 | "description": "Detect attempts to access pods using 'oc debug'", 6 | "rationale": "'oc debug' can be used to access pod contents, potentially exposing sensitive data.", 7 | "remediation": "Review OpenShift audit logs to verify the user, and investigate whether this was legitimate trouble-shooting or malicious activity.", 8 | "disabled": false, 9 | "categories": [ 10 | "Anomalous Activity" 11 | ], 12 | "lifecycleStages": [ 13 | "RUNTIME" 14 | ], 15 | "eventSource": "DEPLOYMENT_EVENT", 16 | "exclusions": [], 17 | "scope": [], 18 | "severity": "HIGH_SEVERITY", 19 | "enforcementActions": [], 20 | "notifiers": [], 21 | "SORTName": "", 22 | "SORTLifecycleStage": "", 23 | "SORTEnforcement": false, 24 | "policyVersion": "1.1", 25 | "policySections": [ 26 | { 27 | "sectionName": "Shell detection", 28 | "policyGroups": [ 29 | { 30 | "fieldName": "Process Name", 31 | "booleanOperator": "OR", 32 | "negate": false, 33 | "values": [ 34 | { 35 | "value": "^.*(sh)$" 36 | } 37 | ] 38 | }, 39 | { 40 | "fieldName": "Process UID", 41 | "booleanOperator": "OR", 42 | "negate": false, 43 | "values": [ 44 | { 45 | "value": "0" 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | ], 52 | "mitreAttackVectors": [], 53 | "criteriaLocked": false, 54 | "mitreVectorsLocked": false, 55 | "isDefault": false 56 | } 57 | ] 58 | } -------------------------------------------------------------------------------- /policies/polkit-execution.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": [ 3 | { 4 | "name": "Polkit Execution Detected", 5 | "description": "Detects execution of the pkexec binary in a container", 6 | "rationale": "Polkit can be abused by attackers to elevate privileges within a container.", 7 | "remediation": "Use your package manager's \"remove\" command to remove polkit packages from the image build for production containers.", 8 | "disabled": false, 9 | "categories": [ 10 | "Security Best Practices" 11 | ], 12 | "lifecycleStages": [ 13 | "RUNTIME" 14 | ], 15 | "eventSource": "DEPLOYMENT_EVENT", 16 | "exclusions": [], 17 | "scope": [], 18 | "severity": "MEDIUM_SEVERITY", 19 | "enforcementActions": [], 20 | "notifiers": [], 21 | "SORTName": "Polkit Execution Detected", 22 | "SORTLifecycleStage": "RUNTIME", 23 | "SORTEnforcement": false, 24 | "policyVersion": "1.1", 25 | "policySections": [ 26 | { 27 | "sectionName": "", 28 | "policyGroups": [ 29 | { 30 | "fieldName": "Process Name", 31 | "booleanOperator": "OR", 32 | "negate": false, 33 | "values": [ 34 | { 35 | "value": "pkexec" 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | ], 42 | "mitreAttackVectors": [], 43 | "criteriaLocked": true, 44 | "mitreVectorsLocked": true, 45 | "isDefault": false 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /policies/polkit-in-image.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": [ 3 | { 4 | "name": "Polkit in Image", 5 | "description": "Alert on deployments with Polkit present", 6 | "rationale": "Leaving privileged administration tools like Polkit in an image potentially allows attackers to escalate privileges within the container.", 7 | "remediation": "Use your package manager's \"remove\" command to remove polkit packages from the image build for production containers.", 8 | "disabled": false, 9 | "categories": [ 10 | "Security Best Practices" 11 | ], 12 | "fields": null, 13 | "lifecycleStages": [ 14 | "BUILD", 15 | "DEPLOY" 16 | ], 17 | "eventSource": "NOT_APPLICABLE", 18 | "whitelists": [], 19 | "exclusions": [], 20 | "scope": [], 21 | "severity": "LOW_SEVERITY", 22 | "enforcementActions": [], 23 | "notifiers": [], 24 | "SORTName": "", 25 | "SORTLifecycleStage": "", 26 | "SORTEnforcement": false, 27 | "policyVersion": "1.1", 28 | "policySections": [ 29 | { 30 | "sectionName": "", 31 | "policyGroups": [ 32 | { 33 | "fieldName": "Image Component", 34 | "booleanOperator": "OR", 35 | "negate": false, 36 | "values": [ 37 | { 38 | "value": "polkit=" 39 | }, 40 | { 41 | "value": "policykit-1=" 42 | } 43 | ] 44 | } 45 | ] 46 | } 47 | ], 48 | "mitreAttackVectors": [], 49 | "criteriaLocked": false, 50 | "mitreVectorsLocked": false, 51 | "isDefault": false 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /terraform/azure-sentinel/README.md: -------------------------------------------------------------------------------- 1 | # Azure Log Analytics - Sentinel Terraform 2 | 3 | This terraform script will provide all resources to setup an integration with Sentinel and Log Analytics Workspace. 4 | 5 | This terraform script will provision following resources: 6 | 7 | - Resource group 8 | - Log Analytics Workspace 9 | - Data Collection Endpoint 10 | - Data Collection Rule 11 | 12 | This script can be used to provision a custom environment and is used for CI testing. 13 | 14 | For more information visit the documentation in the [stackrox repo's Sentinel notifier](https://github.com/stackrox/stackrox/tree/master/central/notifiers/microsoftsentinel). 15 | 16 | ## Quick start 17 | 18 | Requirements: 19 | 20 | - Install azcli 21 | - Authenticating via a [Service Principal](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret) 22 | - Access to the Microsoft Azure StackRox tenant 23 | - Access to bitwarden 24 | 25 | ``` 26 | # Login into Azure, select the subscription. 27 | $ az login 28 | 29 | $ export ARM_SUBSCRIPTION_ID="" 30 | $ export ARM_CLIENT_SECRET="" 31 | $ export ARM_TENANT_ID="" 32 | $ export ARM_CLIENT_ID="" 33 | 34 | $ terraform init 35 | $ terraform fmt 36 | $ terraform validate 37 | $ terraform apply 38 | ``` 39 | 40 | For later reference example Data Collection Rule configuration: https://github.com/hashicorp/terraform-provider-azurerm/blob/main/examples/azure-monitoring/data-collection-rule/main.tf 41 | 42 | ### Create a service principal 43 | 44 | In case you need a new service principal you can run the command below. Please only use this if you are 45 | sure you need new credentials. Make sure to save them in bitwarden. 46 | 47 | ``` 48 | # Create a service principal for authentication 49 | $ az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/$ARM_SUBSCRIPTION_ID" 50 | 51 | { 52 | "appId": "", 53 | "displayName": "azure-cli-2024-10-07-08-49-10", 54 | "password": "", 55 | "tenant": "" 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /terraform/azure-sentinel/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "rg" { 2 | name = "${var.prefix}-resources" 3 | location = var.region 4 | } 5 | 6 | resource "azurerm_monitor_data_collection_endpoint" "endpoint" { 7 | name = "${var.prefix}-data-collection-endpoint" 8 | resource_group_name = azurerm_resource_group.rg.name 9 | location = azurerm_resource_group.rg.location 10 | public_network_access_enabled = true 11 | 12 | lifecycle { 13 | create_before_destroy = true 14 | } 15 | } 16 | 17 | resource "azurerm_log_analytics_workspace" "logworkspace" { 18 | name = "${var.prefix}-log-analytics-workspace" 19 | location = azurerm_resource_group.rg.location 20 | resource_group_name = azurerm_resource_group.rg.name 21 | sku = "PerGB2018" 22 | retention_in_days = 30 23 | } 24 | 25 | # We are using the azapi provider to create custom tables because it is unsupported in the Azure provider. 26 | # This resource links to the data_flow.output_stream field in the `azurerm_monitor_data_collection_rule` resource. 27 | resource "azapi_resource" "data_collection_logs_table" { 28 | name = "stackrox_alerts_CL" 29 | parent_id = azurerm_log_analytics_workspace.logworkspace.id 30 | type = "Microsoft.OperationalInsights/workspaces/tables@2022-10-01" 31 | body = jsonencode( 32 | { 33 | "properties" : { 34 | "schema" : { 35 | "name" : "stackrox_alerts_CL", 36 | "columns" : [ 37 | { 38 | "name" : "TimeGenerated", 39 | "type" : "datetime", 40 | "description" : "The time at which the data was generated" 41 | }, 42 | { 43 | "name" : "msg", 44 | "type" : "dynamic", 45 | "description" : "StackRox alert message sent by a notifer" 46 | } 47 | ] 48 | }, 49 | "retentionInDays" : 30, 50 | "totalRetentionInDays" : 30 51 | } 52 | } 53 | ) 54 | } 55 | 56 | # Data Collection Rule 57 | resource "azurerm_monitor_data_collection_rule" "rule" { 58 | name = "${var.prefix}-data-collection-rule" 59 | resource_group_name = azurerm_resource_group.rg.name 60 | location = azurerm_resource_group.rg.location 61 | data_collection_endpoint_id = azurerm_monitor_data_collection_endpoint.endpoint.id 62 | description = "StackRox data collection rule to forward StackRox alerts to the Log Analytics Workspace." 63 | 64 | destinations { 65 | log_analytics { 66 | workspace_resource_id = azurerm_log_analytics_workspace.logworkspace.id 67 | name = "destination-logs" 68 | } 69 | } 70 | 71 | data_flow { 72 | streams = ["Custom-stackrox_alerts_CL"] 73 | destinations = ["destination-logs"] 74 | 75 | # From `data_collection_logs_table.name`. The prefix is prepended by Azure automatically. 76 | output_stream = "Custom-stackrox_alerts_CL" 77 | } 78 | 79 | stream_declaration { 80 | stream_name = "Custom-stackrox_alerts_CL" 81 | column { 82 | name = "msg" 83 | type = "dynamic" 84 | } 85 | column { 86 | name = "TimeGenerated" 87 | type = "datetime" 88 | } 89 | } 90 | 91 | depends_on = [ 92 | azurerm_log_analytics_workspace.logworkspace, 93 | azapi_resource.data_collection_logs_table 94 | ] 95 | } 96 | 97 | -------------------------------------------------------------------------------- /terraform/azure-sentinel/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "~> 4.4.0" 6 | } 7 | azapi = { 8 | source = "Azure/azapi" 9 | version = "~> 1.8.0" 10 | } 11 | } 12 | 13 | required_version = ">= 1.1.0" 14 | } 15 | 16 | provider "azurerm" { 17 | features {} 18 | } 19 | -------------------------------------------------------------------------------- /terraform/azure-sentinel/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | default = "westus2" 4 | description = "The Azure region where the resources should be deployed to." 5 | } 6 | 7 | variable "prefix" { 8 | type = string 9 | default = "stackrox-sentinel" 10 | } 11 | -------------------------------------------------------------------------------- /util-scripts/acs-correlation-example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi9:9.1.0-1782 2 | 3 | # #ENV Variables 4 | # ENV APP_MODULE testapp:app 5 | # ENV APP_CONFIG gunicorn.conf.py 6 | 7 | #Change User 8 | USER 0 9 | 10 | # Install the required software 11 | RUN dnf install -y wget yum-utils make gcc openssl-devel bzip2-devel libffi-devel zlib-devel && \ 12 | wget https://www.python.org/ftp/python/3.10.8/Python-3.10.8.tgz && \ 13 | tar xzf Python-3.10.8.tgz && \ 14 | cd Python-3.10.8 && \ 15 | ./configure --with-system-ffi --with-computed-gotos --enable-loadable-sqlite-extensions && \ 16 | make altinstall && \ 17 | cd .. && \ 18 | rm Python-3.10.8.tgz 19 | 20 | # # Install pip 21 | # RUN curl -O https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python3 get-pip.py 22 | 23 | #Make Application Directory 24 | RUN mkdir ./app && cd ./app && echo python -V 25 | 26 | # Copy Files into containers 27 | COPY ./ ./app 28 | 29 | #WORKDIR 30 | WORKDIR ./app 31 | 32 | #Install App Dependecies 33 | RUN pip3.10 install -r requirements.txt && pip3.10 install --upgrade pip 34 | 35 | #Expose Ports 36 | EXPOSE 8080/tcp 37 | 38 | #Change Permissions to allow not root-user work 39 | RUN chmod -R g+rw ./ 40 | 41 | #Change User 42 | USER 1001 43 | 44 | #ENTRY 45 | ENTRYPOINT python3.10 app.py 46 | -------------------------------------------------------------------------------- /util-scripts/acs-correlation-example/README.md: -------------------------------------------------------------------------------- 1 | # Prototype application to parse multiple ACS endpoints collect metadata via the API, correlate data and parse out JSON files. 2 | - Will parse out a hierarchical Cluster->Namespace->Deployment->Alerts Relationship 3 | - Can be extended to parse out other relationships 4 | 5 | # Configuration 6 | - Configuration settings are mostly obtained from enviromnent variables. Configuration settings are provided and explained in [config file](./config.py) 7 | 8 | - The list of endpoints for the app to poll can be set via ENDPOINT_LIST_JSON_PATH environment variable. The environment variable should point to a json file with API details. A sample file is provided in [endpoint_list.json](./endpoint_list.json).While environment details are provided via the previously mentioned variable the token used for connection is obtained via enviroment variable. And the token environment variable must be set in the endpoint json file and defined by field "endpoint_token_env_variable_name". 9 | 10 | - Generate's sample output files in output folder, but can be customized for other use cases. 11 | - cluster_namespace_deployment_alert.json: JSON file with hierarchical Cluster->Namespace->Deployment->Alerts relationship. 12 | - endpoint_policy_alert_count.json: JSON file with ACS Endpoint -> Policy -> AlertCount Relationship 13 | 14 | # Run with Podman 15 | - Build Image 16 | ```bash 17 | podman build -t quick_acs_app . 18 | ``` 19 | 20 | - Run Container 21 | ```bash 22 | podman run --env $MAIN_ACS_TOKEN --env OUTPUT_FOLDER=/output -v /tmp/output:/output:Z localhost/quick_acs_app 23 | ``` -------------------------------------------------------------------------------- /util-scripts/acs-correlation-example/config.py: -------------------------------------------------------------------------------- 1 | from pydantic_settings import BaseSettings 2 | import os 3 | from dotenv import load_dotenv # pylint: disable=import-error 4 | 5 | basedir = os.path.abspath(os.path.dirname(__file__)) 6 | load_dotenv(os.path.join(basedir, '.env')) 7 | 8 | class Settings(BaseSettings): 9 | """Application Settings """ 10 | 11 | #If multiple versions of this Applications Hostname are running, this will help identify them 12 | instance_hostname:str = os.environ.get('HOSTNAME') or 'demo_app' 13 | 14 | #File Path where we can look for the list of ACS API Endpoints to work with 15 | endpoint_list_json_path:str = os.environ.get('ENDPOINT_LIST_JSON_PATH') or 'endpoint_list.json' 16 | 17 | #Health Check Retry Count 18 | health_check_retry_count:int = os.environ.get('HEALTH_CHECK_RETRY_COUNT') or 3 19 | 20 | #Health Check Retry Delay in Seconds 21 | health_check_retry_delay:int = os.environ.get('HEALTH_CHECK_RETRY_DELAY') or 10 22 | 23 | #Poll Disabled Policy Information 24 | poll_disabled_policy_info:bool = os.environ.get('POLL_DISABLED_POLICY_INFO') or False 25 | 26 | #Output folder for the Policy output 27 | output_folder:str = os.environ.get('OUTPUT_FOLDER') or 'output' 28 | 29 | settings = Settings() 30 | 31 | 32 | -------------------------------------------------------------------------------- /util-scripts/acs-correlation-example/endpoint_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoints": [ 3 | { 4 | "endpoint_name": "ACS_Demo_Environment", 5 | "endpoint_url": "https://central-rhacs-operator.apps.cluster11.sandbox2585.opentlc.com", 6 | "endpoint_url_description": "ACS API endpoint for the application to make requess to", 7 | "endpoint_token_env_variable_name": "MAIN_ACS_TOKEN", 8 | "verify_endpoint_ssl" : "False", 9 | "endpoint_token_env_variable_name_description": "Environment Variable to retrieve the Token for this cluster" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /util-scripts/acs-correlation-example/logging.conf: -------------------------------------------------------------------------------- 1 | 2 | [loggers] 3 | keys=root 4 | 5 | [handlers] 6 | keys=consoleHandler,detailedConsoleHandler 7 | 8 | [formatters] 9 | keys=normalFormatter,detailedFormatter 10 | 11 | [logger_root] 12 | level=DEBUG 13 | handlers=consoleHandler 14 | 15 | [handler_consoleHandler] 16 | class=StreamHandler 17 | level=INFO 18 | formatter=normalFormatter 19 | args=(sys.stdout,) 20 | 21 | [handler_detailedConsoleHandler] 22 | class=StreamHandler 23 | level=DEBUG 24 | formatter=detailedFormatter 25 | args=(sys.stdout,) 26 | 27 | [formatter_normalFormatter] 28 | format=%(asctime)s loglevel=%(levelname)-6s logger=%(name)s %(funcName)s() L%(lineno)-4d %(message)s 29 | 30 | [formatter_detailedFormatter] 31 | format=%(asctime)s loglevel=%(levelname)-6s logger=%(name)s %(funcName)s() L%(lineno)-4d %(message)s call_trace=%(pathname)s L%(lineno)-4d -------------------------------------------------------------------------------- /util-scripts/acs-correlation-example/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==23.2.1 2 | annotated-types==0.7.0 3 | anyio==4.4.0 4 | certifi==2024.7.4 5 | click==8.1.7 6 | dnspython==2.6.1 7 | email_validator==2.1.1 8 | exceptiongroup==1.2.1 9 | fastapi==0.111.0 10 | fastapi-cli==0.0.4 11 | h11==0.14.0 12 | httpcore==1.0.5 13 | httptools==0.6.1 14 | httpx==0.27.0 15 | idna==3.7 16 | Jinja2==3.1.4 17 | markdown-it-py==3.0.0 18 | MarkupSafe==2.1.5 19 | mdurl==0.1.2 20 | orjson==3.10.4 21 | pydantic==2.7.4 22 | pydantic-settings==2.3.3 23 | pydantic_core==2.18.4 24 | Pygments==2.18.0 25 | python-dotenv==1.0.1 26 | python-multipart==0.0.9 27 | PyYAML==6.0.1 28 | rich==13.7.1 29 | shellingham==1.5.4 30 | sniffio==1.3.1 31 | starlette==0.37.2 32 | typer==0.12.3 33 | typing_extensions==4.12.2 34 | ujson==5.10.0 35 | uuid==1.30 36 | uvicorn==0.30.1 37 | uvloop==0.19.0 38 | watchfiles==0.22.0 39 | websockets==12.0 40 | -------------------------------------------------------------------------------- /util-scripts/compliance-scans-classifications/stackrox_classifications.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #here is how to use the API to push a logon banner as well as header and footers for classification. 4 | # ac - 8/18/2020 5 | 6 | central_server=stackrox.dockr.life:443 7 | 8 | ######### 9 | 10 | function get_password (){ 11 | #read the admin password 12 | echo -n " - StackRox Admin Password for $serverUrl: "; read -s password; echo 13 | } 14 | 15 | #gov logon message 16 | export gov_message=$(cat < /dev/null 2>&1 26 | ;; 27 | 28 | S ) 29 | #secret 30 | get_password 31 | curl -sk -X PUT -u admin:$password https://$central_server/v1/config -d '{"config":{"publicConfig":{"loginNotice":{"enabled":true,"text":"'"$gov_message"'"},"header":{"enabled":true,"text":"SECRET","size":"MEDIUM","color":"#ffffff","backgroundColor":"#d9534f"},"footer":{"enabled":true,"text":"SECRET","size":"MEDIUM","color":"#ffffff","backgroundColor":"#d9534f"}},"privateConfig":{"alertConfig":{"resolvedDeployRetentionDurationDays":7,"deletedRuntimeRetentionDurationDays":7,"allRuntimeRetentionDurationDays":30},"imageRetentionDurationDays":7}}}' > /dev/null 2>&1 32 | ;; 33 | 34 | TS ) 35 | #top secret 36 | get_password 37 | curl -sk -X PUT -u admin:$password https://$central_server/v1/config -d '{"config":{"publicConfig":{"loginNotice":{"enabled":true,"text":"'"$gov_message"'"},"header":{"enabled":true,"text":"TOP SECRET","size":"MEDIUM","color":"#ffffff","backgroundColor":"#f0ad4e"},"footer":{"enabled":true,"text":"TOP SECRET","size":"MEDIUM","color":"#ffffff","backgroundColor":"#f0ad4e"}},"privateConfig":{"alertConfig":{"resolvedDeployRetentionDurationDays":7,"deletedRuntimeRetentionDurationDays":7,"allRuntimeRetentionDurationDays":30},"imageRetentionDurationDays":7}}}' > /dev/null 2>&1 38 | ;; 39 | 40 | clear ) 41 | #clear 42 | get_password 43 | curl -sk -X PUT -u admin:$password https://$central_server/v1/config -d '{"config":{"publicConfig":{"loginNotice":{"enabled":false,"text":"'"$gov_message"'"},"header":{"enabled":false,"text":"","size":"MEDIUM","color":"#ffffff","backgroundColor":"#f0ad4e"},"footer":{"enabled":false,"text":"","size":"MEDIUM","color":"#ffffff","backgroundColor":"#f0ad4e"}},"privateConfig":{"alertConfig":{"resolvedDeployRetentionDurationDays":7,"deletedRuntimeRetentionDurationDays":7,"allRuntimeRetentionDurationDays":30},"imageRetentionDurationDays":7}}}' > /dev/null 2>&1 44 | ;; 45 | 46 | *) echo "Usage: $0 {clear | TS | S | U}"; exit 1 47 | 48 | esac 49 | -------------------------------------------------------------------------------- /util-scripts/compliance-scans-classifications/stackrox_compliance_scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # clemenko@stackrox.com 3 | 4 | standardId="NIST_800_190" 5 | #supported standards CIS_Kubernetes_v1_5 HIPAA_164 NIST_800_190 NIST_SP_800_53_Rev_4 PCI_DSS_3_2 CIS_Docker_v1_2_0 6 | ###### no more edits 7 | 8 | RED=$(tput setaf 1) 9 | GREEN=$(tput setaf 2) 10 | NORMAL=$(tput sgr0) 11 | 12 | function setup () { # setup role and token 13 | 14 | echo -e "\n Creating the API token. Admin password required. " 15 | #read the admin password 16 | echo -n " - StackRox Admin Password for $serverUrl: "; read -s password; echo 17 | 18 | # check to see if the role is there. 19 | if [ $(curl -sk -u admin:$password https://$serverUrl/v1/roles | jq '.roles[] | select(.name=="Compliance")' | wc -l) = 0 ]; then 20 | # create the role 21 | curl -sk -u admin:$password -X POST 'https://'$serverUrl'/v1/roles/Compliance' \ 22 | -H 'accept: application/json, text/plain, */*' \ 23 | -d '{"name":"Compliance","globalAccess":"NO_ACCESS","resourceToAccess":{"Cluster": "READ_ACCESS","Compliance":"READ_WRITE_ACCESS","ComplianceRunSchedule":"READ_WRITE_ACCESS","ComplianceRuns":"READ_WRITE_ACCESS"}}' 24 | fi 25 | 26 | # create token with new role 27 | curl -sk -X POST -u admin:$password https://$serverUrl/v1/apitokens/generate -d '{"name":"compliance","role":null,"roles":["Compliance"]}'| jq -r .token > stackrox_api.token 28 | 29 | echo -e "\n----------------------------------------------------------------------------------" 30 | } 31 | 32 | echo -e "\n StackRox Compliance Automation Script" 33 | echo " - Inputs: ./stackrox_compliance_scan.sh " 34 | echo " - Outputs: ___Results_$(date +"%m-%d-%y").json" 35 | echo -e "----------------------------------------------------------------------------------\n" 36 | 37 | serverUrl=$1 38 | if [ -z $serverUrl ]; then echo "$RED [warn]$NORMAL Please add the server name to the command."; echo ""; exit; fi 39 | 40 | # if stackrox_api.token exists 41 | if [ ! -f stackrox_api.token ]; then setup; fi 42 | 43 | # get api 44 | export token=$(cat stackrox_api.token) 45 | 46 | echo -n "Running $standardId scan on $serverUrl " 47 | 48 | #get clusterId 49 | clusterId=$(curl -sk -H "Authorization: Bearer $token" https://$serverUrl/v1/clusters | jq -r .clusters[0].id) 50 | 51 | clusterName=$(curl -sk -H "Authorization: Bearer $token" https://$serverUrl/v1/clusters/$clusterId | jq -r .cluster.name) 52 | 53 | runId=$(curl -sk -X POST -H "Authorization: Bearer $token" https://$serverUrl/v1/compliancemanagement/runs -d '{"selection": { "clusterId": "'"$clusterId"'", "standardId": "'"$standardId"'" }}' | jq -r .startedRuns[0].id) 54 | 55 | until [ "$(curl -sk -H "Authorization: Bearer $token" https://$serverUrl/v1/complianceManagement/runs | jq -r '.complianceRuns[]|select(.id=="'"$runId"'") | .state' )" == "FINISHED" ]; do echo -n "."; sleep 1; done 56 | 57 | curl -sk -H "Authorization: Bearer $token" https://$serverUrl/v1/compliance/runresults?clusterId="$clusterId"'&standardId='$standardId'&runId='$runId'' | jq . > "$serverUrl"_"$clusterName"_"$standardId"_Results_$(date +"%m-%d-%y").json 58 | 59 | echo -e "$GREEN" "[ok]" "$NORMAL\n" 60 | -------------------------------------------------------------------------------- /util-scripts/component-details-to-csv/README.md: -------------------------------------------------------------------------------- 1 | This script exports component details to CSV, including: cluster, namespace, deployment, image, component and component version. 2 | 3 | Required Environment Vars: 4 | 5 | ROX_ENDPOINT - Host for StackRox central (central.example.com) 6 | ROX_API_TOKEN - Token data from StackRox API token 7 | Required Argument: 8 | 9 | $1 = path/to/output_file.csv 10 | Usage "component_details_csv.sh " 11 | -------------------------------------------------------------------------------- /util-scripts/component-details-to-csv/component_details_csv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Purpose: Dump component name and version to CSV (along with cluster, namespace, deployment and image names) 4 | # Requires: curl, jq 5 | # Requires: $ROX_API_TOKEN = contains an API token with at least the following permissions: 6 | # Cluster (read), Deployment (read), Image (read) 7 | # Requires: $ROX_ENDPOINT = hostname/address of Central (:port if not 443) 8 | # Notes: 9 | # 1) /v1/deployments?query=Cluster:\"$clusterName\" has a limit of 1,000 10 | # deployments per page. This script will paginate, and the parameters can be 11 | # tuned for any environment. 12 | # 2) Tested with StackRox 3.0.56.1 13 | # TODOs: 14 | # 1) add parameters for the ROX_API_TOKEN as --token or --token-file 15 | # 2) add parameters for ROX_ENDPOINT as --endpoint or -e 16 | # 3) add parameters for pagination 17 | # 4) add parameters for cluster and namespace 18 | # References: 19 | # https://help.stackrox.com/docs/use-the-api/ 20 | # https://help.stackrox.com/docs/manage-user-access/manage-role-based-access-control/ 21 | 22 | IFS=$'\n' # make newlines the only separator to allow for cluster names with spaces 23 | 24 | function curl_central() { 25 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/$1" 26 | } 27 | 28 | if [[ -z "${ROX_ENDPOINT}" ]]; then 29 | echo >&2 "ROX_ENDPOINT must be set" 30 | exit 1 31 | fi 32 | 33 | if [[ -z "${ROX_API_TOKEN}" ]]; then 34 | echo >&2 "ROX_API_TOKEN must be set" 35 | exit 1 36 | fi 37 | 38 | if [[ -z "$1" ]]; then 39 | echo >&2 "usage: component_details.sh " 40 | exit 1 41 | fi 42 | 43 | outputFile="$1" 44 | echo "clusterName,namespace,deploymentName,imageName,componentName,componentVersion" > "${outputFile}" 45 | 46 | totalImagesReported=0 47 | 48 | # Iterate clusters - alternatively hardcode the clusterId or name as a parameter 49 | clusters=$(curl_central "v1/clusters" | jq -c '.clusters[] | {name, id}') 50 | for cluster in $clusters 51 | do 52 | clusterName=$(echo "$cluster" | jq -r '.name') 53 | export clusterName 54 | echo "Iterating cluster: $clusterName" 55 | 56 | # Get the deployments for the cluster 57 | # for help with pagination refer to https://help.stackrox.com/docs/use-the-api/#pagination 58 | pagingCounter=0 # counter for records in current page 59 | paginationLimit=100 60 | paginationOffset=0 61 | deployments=$(curl_central "v1/deployments?query=Cluster:\"$clusterName\"&pagination.limit=$paginationLimit&pagination.offset=$paginationOffset" \ 62 | | jq -c '.deployments[] | {id, name, clusterId, namespace}') 63 | while [[ -n $deployments ]] 64 | do 65 | #echo $deployments 66 | echo "There are $(echo "$deployments" | jq -c | wc -l | tr -d '[:blank:]') deployments in this page for cluster $clusterName." 67 | 68 | # reset the counter for this page 69 | pagingCounter=0 70 | 71 | # Iterate the deployments 72 | for deployment in $deployments 73 | do 74 | #echo 75 | #echo 76 | #echo "Deployment result:" 77 | #echo "$deployment" 78 | 79 | # increment the paging counter 80 | pagingCounter=$((pagingCounter+1)) 81 | #echo "pagingCounter = $pagingCounter" 82 | 83 | if [[ $(echo "${deployment}" | jq -rc .name) == null ]]; then 84 | continue; 85 | fi 86 | 87 | deploymentName=$(echo "$deployment" | jq -r '.name') 88 | export deploymentName 89 | deploymentId=$(echo "$deployment" | jq -r '.id') 90 | namespace=$(echo "$deployment" | jq -r '.namespace') 91 | export namespace 92 | 93 | echo $'\t'"Iterating deployment $deploymentName" 94 | 95 | deploymentDetails=$(curl_central "v1/deployments/${deploymentId}") 96 | #echo "$deploymentDetails" 97 | if [[ "$(echo "${deploymentDetails}" | jq -rc .name)" == null ]]; then 98 | continue; 99 | fi 100 | if [[ "$(echo "${deploymentDetails}" | jq '.containers | length')" == "0" ]]; then 101 | continue; 102 | fi 103 | 104 | # Iterate over all images within the deployment and render the CSV Lines 105 | for imageId in $(echo "${deploymentDetails}" | jq -r 'select(.containers != null) | .containers[].image.id'); do 106 | if [[ "${imageId}" != "" ]]; then 107 | image="$(curl_central "v1/images/${imageId}" | jq -rc)" 108 | if [[ "$(echo "${image}" | jq -rc .name)" == null ]]; then 109 | continue; 110 | fi 111 | 112 | imageName=$(echo "${image}" | jq -rc '.name.fullName') 113 | export imageName 114 | echo $'\t\t'"Iterating image $imageName" 115 | 116 | #debug 117 | #echo 118 | #echo 119 | #echo "Image result:" 120 | #echo $image 121 | 122 | # Format the CSV correctly 123 | echo "${image}" | jq -r '(.metadata.v1.layers as $layers | .scan.components | sort_by(.layerIndex, .name) | .[] | . as $component | [ env.clusterName, env.namespace, env.deploymentName, env.imageName, $component.name, $component.version ]) | @csv' >> "${outputFile}" 124 | 125 | totalImagesReported=$((totalImagesReported + 1)) 126 | fi 127 | done 128 | 129 | done # ends for deployment in $deployments 130 | 131 | paginationOffset=$((paginationOffset + pagingCounter)) 132 | #echo "paginationOffset = $paginationOffset" 133 | deployments=$(curl_central "v1/deployments?query=Cluster:\"$clusterName\"&pagination.limit=$paginationLimit&pagination.offset=$paginationOffset" \ 134 | | jq -c '.deployments[] | {id, name, clusterId, namespace}') 135 | done # ends while for paging 136 | done # ends for cluster in $clusters 137 | 138 | echo "Script complete. A total of $totalImagesReported images have component details included in $outputFile." 139 | -------------------------------------------------------------------------------- /util-scripts/cronjob-upload-vuln-definitions/fetchvulns-cronjob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: fetch-vuln-definitions 5 | namespace: stackrox 6 | spec: 7 | schedule: "*/1 * * * *" 8 | jobTemplate: 9 | spec: 10 | template: 11 | spec: 12 | securityContext: 13 | runAsUser: 0 14 | dnsPolicy: ClusterFirst 15 | serviceAccountName: vuln-loader 16 | restartPolicy: Never 17 | containers: 18 | - name: vuln-fetcher 19 | image: registry.access.redhat.com/ubi8/ubi:8.6 20 | resources: {} 21 | command: 22 | - /opt/fetchvulns.sh 23 | volumeMounts: 24 | - name: scripts 25 | mountPath: /opt 26 | volumes: 27 | - name: scripts 28 | configMap: 29 | name: vulnfetcher 30 | defaultMode: 0555 31 | -------------------------------------------------------------------------------- /util-scripts/cronjob-upload-vuln-definitions/upload-vulns-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: vulnfetcher 5 | namespace: stackrox 6 | data: 7 | fetchvulns.sh: | 8 | #!/bin/bash 9 | curl https://install.stackrox.io/scanner/scanner-vuln-updates.zip --output vulns.zip 10 | export ROX_API_TOKEN= 11 | export ROX_CENTRAL_ADDRESS= 12 | curl -O https://mirror.openshift.com/pub/rhacs/assets/4.1.2/bin/Linux/roxctl 13 | chmod +x roxctl 14 | ./roxctl scanner upload-db -e "$ROX_CENTRAL_ADDRESS" --insecure-skip-tls-verify --scanner-db-file=vulns.zip 15 | -------------------------------------------------------------------------------- /util-scripts/export-all-policies/README.md: -------------------------------------------------------------------------------- 1 | # Export all policies 2 | 3 | This script uses the StackRox API to export all policies (both user defined and system) to a JSON file. 4 | 5 | **Required Environment Vars:** 6 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 7 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/4.2/cli/using-the-roxctl-cli.html#authenticating-by-using-the-roxctl-cli_using-roxctl-cli) 8 | 9 | **Required Tools:** 10 | * `jq` is used by this script and must be installed. Installation instructions for various platforms can be found [here](https://stedolan.github.io/jq/download/) 11 | 12 | **Usage** 13 | `./export-all-policies.sh ` 14 | -------------------------------------------------------------------------------- /util-scripts/export-all-policies/export-all-policies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Purpose: Export all policies to JSON 4 | # Requires: 5 | # - curl, jq 6 | # - $ROX_API_TOKEN = contains an API token with the following permissions: 7 | # Policy (read) - for example a token with the StackRox 'Analyst' role 8 | # - $ROX_ENDPOINT = hostname/address of StackRox Central (:port if not 443) 9 | # Actions: 10 | # - Exports all policies from ACS to JSON. 11 | 12 | if [[ -z "${ROX_ENDPOINT}" ]]; then 13 | echo >&2 "Environment variable ROX_ENDPOINT must be set" 14 | echo >&2 "export ROX_ENDPOINT=stackrox.example.com" 15 | exit 1 16 | fi 17 | 18 | if [[ -z "${ROX_API_TOKEN}" ]]; then 19 | echo >&2 "Environment variable ROX_API_TOKEN must be set" 20 | echo >&2 "export ROX_API_TOKEN=$(cat token-file)" 21 | exit 1 22 | fi 23 | 24 | if ! [[ -x "$(command -v curl)" ]]; then 25 | echo >&2 "curl does not exist or is not in the PATH" 26 | exit 1 27 | fi 28 | 29 | if ! [[ -x "$(command -v jq)" ]]; then 30 | echo >&2 "jq does not exist or is not in the PATH" 31 | exit 1 32 | fi 33 | 34 | if [[ -z "$1" ]]; then 35 | echo >&2 "usage: export-all-policies.sh " 36 | exit 1 37 | fi 38 | output_file="$1" 39 | 40 | 41 | policies=$(curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/v1/policies" | jq -r '[.policies[].id] | @csv') 42 | curl -sk -X POST -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/v1/policies/export" --data-raw "{\"policyIds\":[${policies}]}" | jq -r . > "${output_file}" 43 | -------------------------------------------------------------------------------- /util-scripts/export-cves-to-csv/README.md: -------------------------------------------------------------------------------- 1 | This script exports all CVE data in a Policy violation such as Fixable CVSS >=7, including images, component version, fixed-in version etc. 2 | 3 | Required Environment Vars: 4 | 5 | ROX_ENDPOINT - Host for StackRox central (central.example.com) 6 | ROX_API_TOKEN - Token data from StackRox API token 7 | Required Argument: 8 | 9 | $1 = path/to/output_file.csv 10 | Usage "create-csv.sh " 11 | -------------------------------------------------------------------------------- /util-scripts/export-cves-to-csv/create-csv.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | VERSION="${VERSION:-v1}" 6 | 7 | if [[ -z "${ROX_ENDPOINT}" ]]; then 8 | echo >&2 "ROX_ENDPOINT must be set" 9 | exit 1 10 | fi 11 | 12 | if [[ -z "${ROX_API_TOKEN}" ]]; then 13 | echo >&2 "ROX_API_TOKEN must be set" 14 | exit 1 15 | fi 16 | 17 | if [[ -z "$1" ]]; then 18 | echo >&2 "usage: create-csv.sh " 19 | exit 1 20 | fi 21 | 22 | output_file="$1" 23 | if [[ "${VERSION}" == "v1" ]]; then 24 | echo '"Deployment", "Image", "CVE", "CVSS Score", "Summary", "Severity", "Component", "Version", "Fixed By", "Layer Index", "Layer Instruction"' > "${output_file}" 25 | elif [[ "${VERSION}" == "v2" ]]; then 26 | echo '"Cluster Name", "Cluster Id", "Namespace", "Namespace Id","Deployment", "Image", "CVE", "CVSS Score", "Summary", "Severity", "Component", "Version", "Fixed By", "Layer Index", "Layer Instruction"' > "${output_file}" 27 | else 28 | echo "Unknown version ${VERSION} detected. v1 and v2 supported" 29 | exit 1 30 | fi 31 | 32 | function curl_central() { 33 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/$1" 34 | } 35 | 36 | # Collect all alerts 37 | cvss=7 38 | 39 | res="$(curl_central "v1/alerts?query=Policy%3AFixable%20Severity%20at%20least%20Important")" 40 | 41 | # Iterate over all deployments and get the full deployment 42 | for deployment_id in $(echo "${res}" | jq -r .alerts[].deployment.id); do 43 | deployment_res="$(curl_central "v1/deployments/${deployment_id}")" 44 | if [[ "$(echo "${deployment_res}" | jq -rc .name)" == null ]]; then 45 | continue; 46 | fi 47 | 48 | if [[ "$(echo "${deployment_res}" | jq '.containers | length')" == "0" ]]; then 49 | continue; 50 | fi 51 | 52 | export deployment_name="$(echo "${deployment_res}" | jq -rc .name)" 53 | export namespace="$(echo "${deployment_res}" | jq -rc .namespace)" 54 | export namespaceId="$(echo "${deployment_res}" | jq -rc .namespaceId)" 55 | export clusterName="$(echo "${deployment_res}" | jq -rc .clusterName)" 56 | export clusterId="$(echo "${deployment_res}" | jq -rc .clusterId)" 57 | 58 | # Iterate over all images within the deployment and render the CSV Lines 59 | for image_id in $(echo "${deployment_res}" | jq -r 'select(.containers != null) | .containers[].image.id'); do 60 | if [[ "${image_id}" != "" ]]; then 61 | image_res="$(curl_central "v1/images/${image_id}" | jq -rc)" 62 | if [[ "$(echo "${image_res}" | jq -rc .name)" == null ]]; then 63 | continue; 64 | fi 65 | 66 | image_name="$(echo "${image_res}" | jq -rc '.name.fullName')" 67 | export image_name 68 | 69 | # Format the CSV correctly 70 | if [[ "${VERSION}" == "v1" ]]; then 71 | echo "${image_res}" | jq -r --argjson cvss "$cvss" 'try (.metadata.v1.layers as $layers | .scan.components | sort_by(.layerIndex, .name) | .[]? | . as $component | select(.vulns != null) | .vulns[] | select(.cvss >= $cvss) | select(.fixedBy != null) | [ env.deployment_name, env.image_name, .cve, .cvss, .summary, .severity, $component.name, $component.version, .fixedBy, $component.layerIndex, ($layers[$component.layerIndex // 0].instruction + " " +$layers[$component.layerIndex // 0].value)]) | @csv' >> "${output_file}" 72 | else 73 | echo "${image_res}" | jq -r --argjson cvss "$cvss" 'try (.metadata.v1.layers as $layers | .scan.components | sort_by(.layerIndex, .name) | .[]? | . as $component | select(.vulns != null) | .vulns[] | select(.cvss >= $cvss) | select(.fixedBy != null) | [ env.clusterName, env.clusterId, env.namespace, env.namespaceId, env.deployment_name, env.image_name, .cve, .cvss, .summary, .severity, $component.name, $component.version, .fixedBy, $component.layerIndex, ($layers[$component.layerIndex // 0].instruction + " " +$layers[$component.layerIndex // 0].value)]) | @csv' >> "${output_file}" 74 | fi 75 | fi 76 | done 77 | done 78 | -------------------------------------------------------------------------------- /util-scripts/external-entities/README.md: -------------------------------------------------------------------------------- 1 | # Cluster External Entities Inspection 2 | This tool communicates with the Central API to inspect known external 3 | network entities for a given cluster. These entities are either 4 | pre-defined CIDR blocks for known address ranges, or specific IP addresses. 5 | 6 | The tool can either retrieve all known external entities, or all entities that 7 | have communicated with a given deployment. 8 | 9 | ## Configuration 10 | 11 | ### Environment variables 12 | 13 | - ROX_ENDPOINT: the endpoint for communication with central. This can usually 14 | be found using the following command 15 | 16 | ```sh 17 | oc -n stackrox get route central -o=jsonpath='{.spec.host}' 18 | ``` 19 | 20 | - ROX_API_KEY: an API key generated within the ACS installation. From the UI 21 | this can be created via the integrations page. 22 | 23 | 24 | ## Example Commands 25 | 26 | ```sh 27 | # Get all external entities for the 'remote' cluster 28 | $ ./external-entities.py entities remote 29 | 30 | # Get all external network flows for Central, in the 'remote' cluster 31 | $ ./external-entities.py deployments remote central 32 | 33 | # ... and with json output 34 | $ ./external-entities.py --json deployment remote central 35 | 36 | # Get external entities that match a given CIDR block 37 | $ ./external-entities.py entities remote --cidr 192.168.0.0/24 38 | ``` 39 | -------------------------------------------------------------------------------- /util-scripts/external-entities/external-entities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import argparse 5 | import os 6 | import sys 7 | import json 8 | 9 | import tabulate 10 | 11 | 12 | ROX_ENDPOINT_ENV_KEY = "ROX_ENDPOINT" 13 | ROX_API_TOKEN_ENV_KEY = "ROX_API_TOKEN" 14 | 15 | 16 | def log(msg): 17 | print(msg, file=sys.stderr) 18 | 19 | 20 | def err(msg): 21 | log(msg) 22 | sys.exit(1) 23 | 24 | 25 | class Auth: 26 | def __init__(self, endpoint, api_key): 27 | self.endpoint = endpoint 28 | self.api_key = api_key 29 | 30 | 31 | class Client: 32 | def __init__(self, cluster, auth): 33 | self._auth = auth 34 | self._cluster = cluster 35 | self._cluster_id = None 36 | 37 | def get_all_external_entities(self, discovered=True, cidr=None): 38 | query = f"Discovered External Source:{str(discovered).lower()}" 39 | if cidr: 40 | query = f"{query}+External Source Address:{cidr}" 41 | return self._get(f'v1/networkgraph/cluster/{self.cluster_id()}/externalentities', params={ 42 | "query": query 43 | }) 44 | 45 | def get_external_flows_by_deployment(self, deployment_id, ingress_only=False, egress_only=False): 46 | return self._get(f'v1/networkgraph/cluster/{self.cluster_id()}/externalentities/flows/{deployment_id}', params={ 47 | "ingress_only": ingress_only, 48 | "egress_only": egress_only, 49 | }) 50 | 51 | def get_deployment_id(self, deployment_name): 52 | response = self._get('v1/deployments') 53 | 54 | deployments = list(filter(lambda x: x['name'] == deployment_name, response.get('deployments', []))) 55 | if len(deployments) != 1: 56 | err(f"Unable to find deployment with name: {deployment_name}") 57 | 58 | return deployments[0]['id'] 59 | 60 | def get_cluster_id(self): 61 | response = self._get('v1/clusters', params={ 62 | 'query': f'Cluster:{self._cluster}' 63 | }) 64 | 65 | clusters = response.get('clusters', []) 66 | if len(clusters) != 1: 67 | err(f"Unable to find cluster with name: {self._cluster}") 68 | 69 | cluster = clusters[0] 70 | return cluster["id"] 71 | 72 | def cluster_id(self): 73 | if self._cluster_id is None: 74 | self._cluster_id = self.get_cluster_id() 75 | return self._cluster_id 76 | 77 | def _get(self, path, headers=None, params=None): 78 | if not headers: 79 | headers = {} 80 | 81 | headers.update({ 82 | 'Authorization': f'Bearer {self._auth.api_key}', 83 | }) 84 | 85 | response = requests.get(self._endpoint_path(path), params=params, headers=headers, verify=False) 86 | # There's always a json value regardless of success. 87 | # On failure, it contains the error message 88 | 89 | value = response.json() 90 | if not response.ok: 91 | self._handle_error_response(value, response.status_code) 92 | 93 | return value 94 | 95 | def _endpoint_path(self, path): 96 | return f'https://{self._auth.endpoint}/{path}' 97 | 98 | def _handle_error_response(self, response, status): 99 | if 'error' not in response: 100 | err(f'Error: request failed with status code {status}') 101 | err(f"Error: {response['error']}") 102 | 103 | 104 | def flows_table_output(flows, deployment_name, deployment_id): 105 | print(f'External flows for {deployment_name} ({deployment_id})') 106 | table = [] 107 | for flow in flows.get('flows', []): 108 | try: 109 | props = flow['props'] 110 | src, dest = props['srcEntity'], props['dstEntity'] 111 | 112 | row = [deployment_name] 113 | if src['type'] == 'DEPLOYMENT': 114 | row.extend([dest['externalSource']['name'], dest['externalSource']['cidr'], 'EGRESS']) 115 | else: 116 | row.extend([src['externalSource']['name'], src['externalSource']['cidr'], 'INGRESS']) 117 | 118 | row.extend([props['dstPort'], props['l4protocol']]) 119 | except KeyError: 120 | log(f'failed to parse {flow}') 121 | continue 122 | 123 | table.append(row) 124 | 125 | print(tabulate.tabulate(table, headers=['Deployment', 'Name', 'CIDR', 'Direction', 'Port', 'Proto'])) 126 | 127 | 128 | def endpoints_table_output(endpoints, cluster): 129 | print(f'External entities in cluster {cluster}') 130 | table = [] 131 | for entity in endpoints.get('entities', []): 132 | try: 133 | ip = entity['info']['externalSource']['name'] 134 | cidr = entity['info']['externalSource']['cidr'] 135 | except KeyError: 136 | log(f"failed to parse {entity}") 137 | continue 138 | 139 | table.append([ip, cidr]) 140 | 141 | print(tabulate.tabulate(table, headers=['IP', 'CIDR'])) 142 | 143 | 144 | def entities(args, auth): 145 | client = Client(args.cluster, auth) 146 | endpoints = client.get_all_external_entities(discovered=not args.all, cidr=args.cidr) 147 | if args.json: 148 | json.dump(endpoints, fp=sys.stdout, indent=4) 149 | else: 150 | endpoints_table_output(endpoints, args.cluster) 151 | 152 | 153 | def deployment(args, auth): 154 | client = Client(args.cluster, auth) 155 | deployment_id = client.get_deployment_id(args.deployment) 156 | flows = client.get_external_flows_by_deployment(deployment_id) 157 | if args.json: 158 | json.dump(flows, fp=sys.stdout, indent=4) 159 | else: 160 | flows_table_output(flows, args.deployment, deployment_id) 161 | 162 | 163 | def main(): 164 | parser = argparse.ArgumentParser() 165 | parser.add_argument( 166 | "--rox-endpoint", 167 | default=os.environ.get(ROX_ENDPOINT_ENV_KEY), 168 | help="The URL of Central, e.g. localhost:1234", 169 | ) 170 | parser.add_argument( 171 | "--rox-api-key", 172 | default=os.environ.get(ROX_API_TOKEN_ENV_KEY), 173 | help="The API key from Central. Can be generated from the Integrations page in the ACS UI" 174 | ) 175 | parser.add_argument( 176 | "--json", "-j", action='store_true', 177 | help="Output raw json, instead of a table" 178 | ) 179 | 180 | subparsers = parser.add_subparsers(dest='subcommand') 181 | 182 | ent = subparsers.add_parser('entities', help='Get all entities for a given cluster') 183 | 184 | ent.add_argument('cluster', help="The name of the cluster to inspect") 185 | ent.add_argument('--all', '-a', action='store_true', help='Get all entities (not just discovered entities)') 186 | ent.add_argument("--cidr", "-c", help='Filter by CIDR block') 187 | 188 | deploy = subparsers.add_parser('deployment', help='Get all entities for a given deployment') 189 | 190 | deploy.add_argument("cluster", help="The name of the cluster to inspect") 191 | deploy.add_argument("deployment", help="The name of the deployment to inspect") 192 | 193 | args = parser.parse_args() 194 | if not args.rox_endpoint: 195 | err(f'{ROX_ENDPOINT_ENV_KEY} must be set') 196 | if not args.rox_api_key: 197 | err(f'{ROX_API_TOKEN_ENV_KEY} must be set') 198 | 199 | auth = Auth(args.rox_endpoint, args.rox_api_key) 200 | 201 | if args.subcommand == 'entities': 202 | entities(args, auth) 203 | elif args.subcommand == 'deployment': 204 | deployment(args, auth) 205 | 206 | 207 | if __name__ == '__main__': 208 | main() 209 | -------------------------------------------------------------------------------- /util-scripts/external-entities/requirements.txt: -------------------------------------------------------------------------------- 1 | tabulate==0.9.0 2 | requests==2.25.0 3 | -------------------------------------------------------------------------------- /util-scripts/generate_violations_csv/README.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | ``` 3 | pip install -r requirements.txt 4 | ``` 5 | 6 | ### Set ACS API keys and Central API endpoint 7 | ``` 8 | export acs_api_key=<> 9 | export acs_central_api= 10 | ``` 11 | 12 | ### Expected query arguments 13 | 14 | ##### Limit cluster 15 | 16 | `Cluster:cluster_name1,cluster_name2` 17 | 18 | ``` 19 | python generate_violations_csv.py --query_scope Cluster:dev_cluster,stating_cluster 20 | ``` 21 | 22 | ##### Limit cluster and namespace 23 | 24 | `Cluster:cluster_name+namespace_name` 25 | 26 | ``` 27 | python generate_violations_csv.py --query_scope Cluster:dev_cluster+Namespace:sandbox_namespace 28 | ``` 29 | 30 | #### Pull all violations 31 | 32 | `all` 33 | 34 | ``` 35 | python generate_violations_csv.py --query_scope all 36 | ``` 37 | -------------------------------------------------------------------------------- /util-scripts/generate_violations_csv/generate_violations_csv.py: -------------------------------------------------------------------------------- 1 | import os, shutil 2 | import requests 3 | import pandas as pd 4 | import json 5 | from pandas import json_normalize 6 | from datetime import datetime 7 | import argparse 8 | 9 | 10 | def convert_argsparse(value): 11 | return value.replace('+', '%2B') 12 | 13 | parser = argparse.ArgumentParser(description='Provide filter to ACS query') 14 | parser.add_argument('--query_scope', type=str, help='limit query to cluster/namespace, e.g: Cluster:cluster_name, Cluster:cluster_name+Namespace:namespace_name"', required=True) 15 | args = parser.parse_args() 16 | query_scope = convert_argsparse(args.query_scope) 17 | 18 | 19 | current_date = datetime.now().strftime("%Y-%m-%d") 20 | tmp_workdir = f"reports/tmp" 21 | 22 | violations_csv = f"{tmp_workdir}/violations_raw_{query_scope}_{current_date}.csv" 23 | violations_images_list_tmp = f"{tmp_workdir}/violations_images_list_{query_scope}_{current_date}.txt" 24 | violations_csv_tmp = f"{tmp_workdir}/violations_{query_scope}_{current_date}.csv" 25 | 26 | violations_images_csv = f"reports/violations_images_{query_scope}_{current_date}.csv" 27 | 28 | acs_api_key = os.getenv('acs_api_key') 29 | acs_central_api = os.getenv('acs_central_api') 30 | 31 | def verify_acs_api_key(acs_api_key): 32 | if acs_api_key is None: 33 | raise Exception("ACS API key not found.") 34 | if acs_central_api is None: 35 | raise Exception("ACS Central API endpoint not found.") 36 | 37 | def pull_violations(acs_api_key,query_scope): 38 | offset = 0 39 | limit = 50 40 | all_violations = [] 41 | 42 | while True: 43 | if query_scope == 'all': 44 | endpoint = f"{acs_central_api}/alerts?&pagination.offset={offset}&pagination.limit={limit}&pagination.sortOption.field=Violation Time&pagination.sortOption.reversed=true" 45 | headers = {'Authorization': f'Bearer {acs_api_key}'} 46 | else: 47 | endpoint = f"{acs_central_api}/alerts?query={query_scope}&pagination.offset={offset}&pagination.limit={limit}&pagination.sortOption.field=Violation Time&pagination.sortOption.reversed=true" 48 | headers = {'Authorization': f'Bearer {acs_api_key}'} 49 | 50 | response = requests.get(endpoint, headers=headers) 51 | response_body = response.json() 52 | 53 | if isinstance(response_body, dict) and 'alerts' in response_body: 54 | results = response_body['alerts'] 55 | print(f"INFO : pulled {len(results)} violations") 56 | else: 57 | print("ERROR: no violations found") 58 | results = [] 59 | all_violations.extend(results) 60 | 61 | if len(results) < limit: 62 | break 63 | else: 64 | offset += limit 65 | return all_violations 66 | 67 | 68 | def pull_violations_images(acs_api_key): 69 | violations_images = [] 70 | image_names_list = [] 71 | 72 | alert_ids = [item['id'] for item in violations_data] 73 | 74 | for alert_id in alert_ids: 75 | headers = {'Authorization': f'Bearer {acs_api_key}'} 76 | response = requests.get(f"{acs_central_api}/alerts/{alert_id}",headers=headers) 77 | if response.status_code == 200: 78 | result = response.json() 79 | violations_images.append(result) 80 | 81 | image_names = [] 82 | try: 83 | containers = result['deployment']['containers'] 84 | for container in containers: 85 | full_name = container['image']['name']['fullName'] 86 | image_names.append(full_name) 87 | except KeyError: 88 | print(f"ERROR: failed to find image names for alert_id: {alert_id}") 89 | 90 | if image_names: 91 | output = ','.join(image_names) if len(image_names) > 1 else image_names[0] 92 | image_names_list.append(output) 93 | 94 | 95 | with open(violations_images_list_tmp, 'w') as output_file: 96 | for image_names in image_names_list: 97 | output_file.write(f"{image_names}\n") 98 | 99 | 100 | def construct_violations_csv(violations_csv): 101 | df = pd.read_csv(violations_csv) 102 | columns_to_delete = [0,1,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,21,22,23] 103 | df.drop(df.columns[columns_to_delete], axis=1, inplace=True) 104 | df.to_csv(violations_csv_tmp, index=False) 105 | 106 | df = pd.read_csv(violations_csv_tmp) 107 | with open(violations_images_list_tmp, 'r') as f: 108 | images_list = f.read().splitlines() 109 | df['images'] = images_list 110 | df.to_csv(violations_images_csv , index=False) 111 | 112 | 113 | ##### <<< main >>> ####### 114 | 115 | verify_acs_api_key(acs_api_key) 116 | 117 | os.makedirs(tmp_workdir, exist_ok=True) 118 | 119 | 120 | violations_data = pull_violations(acs_api_key,query_scope) 121 | print(f"INFO : pulled total of {len(violations_data)} violations") 122 | 123 | 124 | if violations_data: 125 | df = json_normalize(violations_data) 126 | else: 127 | df = pd.DataFrame() 128 | df.to_csv(violations_csv , index=False) 129 | 130 | print("INFO : matching image names to violations") 131 | violation_images = pull_violations_images(acs_api_key) 132 | 133 | print("INFO : exporting csv") 134 | construct_violations_csv(violations_csv) 135 | 136 | 137 | shutil.rmtree(tmp_workdir) 138 | -------------------------------------------------------------------------------- /util-scripts/generate_violations_csv/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pandas 3 | -------------------------------------------------------------------------------- /util-scripts/health-check/README.md: -------------------------------------------------------------------------------- 1 | # StackRox Cluster Health Check 2 | 3 | This script reports on the health of all of the attached StackRox Secured Clusters 4 | 5 | **Required Environment Vars:** 6 | 7 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 8 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/3.74/cli/getting-started-cli.html#cli-authentication_cli-getting-started) 9 | 10 | **Required Tools:** 11 | 12 | * `jq` is used by this script and must be installed. Installation instructions for various platforms can be found [here](https://stedolan.github.io/jq/download/) 13 | * `base64` is used for encoding and decoding strings 14 | 15 | **Usage** 16 | `./health-check.sh` 17 | 18 | **Example Output** 19 | 20 | ```bash 21 | Cluster: logan-support-1, Version = 3.0.57.2, Overall = HEALTHY, Sensor = HEALTHY, Collector = HEALTHY, Last Contact = 2021-04-09T00:44:18.076583Z 22 | 23 | Cluster: cluster2, Version = 3.0.56.0, Overall = UNHEALTHY, Sensor = HEALTHY, Collector = DEGRADED, Last Contact = 2021-04-09T00:44:18.076583Z 24 | ``` 25 | -------------------------------------------------------------------------------- /util-scripts/health-check/health-check.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z "${ROX_ENDPOINT}" ]]; then 6 | echo >&2 "ROX_ENDPOINT must be set" 7 | exit 1 8 | fi 9 | 10 | if [[ -z "${ROX_API_TOKEN}" ]]; then 11 | echo >&2 "ROX_API_TOKEN must be set" 12 | exit 1 13 | fi 14 | 15 | function get_clusters() { 16 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/v1/clusters" 17 | } 18 | 19 | cluster_response=$(get_clusters) 20 | 21 | # Loop through clusters 22 | for cluster in $(echo "${cluster_response}" | jq -r -c '.clusters[] | @base64'); do 23 | _jq() { 24 | echo ${cluster} | base64 --decode | jq -r ${1} 25 | } 26 | cluster_name=$(_jq '.name') 27 | health_status=$(_jq '.healthStatus') 28 | sensor_health_status=$(_jq '.healthStatus.sensorHealthStatus') 29 | collector_health_status=$(_jq '.healthStatus.collectorHealthStatus') 30 | overall_health_status=$(_jq '.healthStatus.overallHealthStatus') 31 | last_contact=$(_jq '.healthStatus.lastContact') 32 | version=$(_jq '.status.sensorVersion') 33 | echo "Cluster: ${cluster_name}, Version = ${version}, Overall = ${overall_health_status}, Sensor = ${sensor_health_status}, Collector = ${collector_health_status}, Last Contact = ${last_contact}" 34 | echo "" 35 | done -------------------------------------------------------------------------------- /util-scripts/image-cve-report/README.md: -------------------------------------------------------------------------------- 1 | # image-cve-report 2 | Script which uses Red Hat Advanced Cluster Security integration to generate CSV files that contains CVE vulnerability data for images in a specific or all namespaces in your cluster. 3 | 4 | **Required Environment Vars:** 5 | 6 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 7 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/3.74/cli/getting-started-cli.html#cli-authentication_cli-getting-started) 8 | 9 | **Usage** 10 | To get CSV files for all images in all namespaces 11 | 12 | ``` 13 | ./image-cve-report.sh 14 | ``` 15 | 16 | To get CSV files for all images in the `my-namespace` namespace 17 | 18 | ``` 19 | ./image-cve-report.sh my-namespace 20 | ``` 21 | -------------------------------------------------------------------------------- /util-scripts/image-cve-report/image-cve-report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script pulls out a report on CVEs affecting all images in one or all namespaces 3 | # Requires ROX_ENDPOINT and ROX_API_TOKEN environment variables 4 | # Requires analyst access or more in ACS 5 | # Usage: $0 name-of-namespace-to-scan | nothing-which-scans-all-namespaces 6 | # Magnus Glantz, sudo@redhat.com, 2021, with great help from Neil Carpenter 7 | # and inspiration from https://github.com/stackrox/contributions/blob/main/util-scripts/violations-to-csv/violations-to-csv.sh 8 | 9 | 10 | case $1 in 11 | *help) 12 | echo "$0 namespace-name|nothing-to-scan-all-namespaces" 13 | ;; 14 | esac 15 | 16 | if [[ -z "${ROX_ENDPOINT}" ]]; then 17 | echo >&2 "ROX_ENDPOINT must be set" 18 | exit 1 19 | fi 20 | 21 | if [[ -z "${ROX_API_TOKEN}" ]]; then 22 | echo >&2 "ROX_API_TOKEN must be set" 23 | exit 1 24 | fi 25 | 26 | if [[ -z "${1}" ]]; then 27 | all_namespaces=1 28 | else 29 | all_namespaces=0 30 | namespace=$1 31 | fi 32 | 33 | # Create jq layers file 34 | echo '["CVE", "CVSS Score", "Summary", "Component", "Version", "Fixed By", "Layer Index", "Layer Instruction"], (.metadata.v1.layers as $layers | .scan.components | sort_by(.layerIndex, .name) | .[] | . as $component | select(.vulns != null) | .vulns[] | [.cve, .cvss, .summary, $component.name, $component.version, .fixedBy, $component.layerIndex, ($layers[$component.layerIndex].instruction + " " +$layers[$component.layerIndex].value)]) | @csv' > layers_query 35 | 36 | function curl_central() { 37 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/$1" 38 | } 39 | 40 | # Gather CVEs for images in all or one namespace 41 | 42 | if [ -f results.json ]; then 43 | rm -f results.json 44 | fi 45 | 46 | if [[ "$all_namespaces" -eq 1 ]]; then 47 | for namespace in $(curl_central "v1/namespaces" | jq -r ".namespaces[].metadata.name") 48 | do 49 | for imageid in $(curl_central "v1/images?query=Namespace:r/${namespace}"|jq -r ".images[] | select(.cves != null) | .id") 50 | do 51 | echo "We are in $namespace looking at $imageid" 52 | curl_central v1/images/{$imageid}|jq >>results.json 53 | done 54 | done 55 | elif [[ "$all_namespaces" -eq 0 ]]; then 56 | for imageid in $(curl_central "v1/images?query=Namespace:r/${namespace}"|jq -r ".images[] | select(.cves != null) | .id") 57 | do 58 | echo "We are in $namespace looking at $imageid" 59 | curl_central v1/images/{$imageid}|jq >>results.json 60 | done 61 | fi 62 | 63 | jq -r -f layers_query results.json 64 | -------------------------------------------------------------------------------- /util-scripts/listening-endpoints/README.md: -------------------------------------------------------------------------------- 1 | # Listening Endpoints 2 | 3 | This script reports the listening endpoints in the clusters and what processes are listening on them. 4 | 5 | **Required Environment Vars:** 6 | 7 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 8 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/3.74/cli/getting-started-cli.html#cli-authentication_cli-getting-started) 9 | 10 | **Required Tools:** 11 | 12 | * `jq` is used by this script and must be installed. Installation instructions for various platforms can be found [here](https://stedolan.github.io/jq/download/) 13 | 14 | **Usage:** 15 | All of the command-line parameters are optional. The default is to output all listening endpoints in all clusters in a tabular format. The options can be used in any combination. 16 | `./listening_endpoints.sh deployment= deploymentname= namespace= clustername= clusterid= format=` 17 | 18 | "format" can be json or table. The default is table. 19 | 20 | "deployment" can be used to get the listening endpoints for a specific deployment_id. This is no different than the regular API. 21 | 22 | "deployment_name" can be more convenient as you only need to know the name of the deployment (E.g. stackrox) rather than looking up the deployment_id for the deployment. 23 | If using this option, you might also want to specify the namespace and clustername/cluster_id. 24 | 25 | "namespace" can be used to look up all listening endpoints for a namespace, not just deployment. If you use this option you might also want to specify the clustername/clusterid. 26 | 27 | "clustername" can be used to look up all listening endpoints in a cluster by its name. E.g "production", or "dev". 28 | 29 | "clusterid" can be used to look up all listening endpoints in a cluster by its id. 30 | -------------------------------------------------------------------------------- /util-scripts/listening-endpoints/listening-endpoints.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eou pipefail 3 | 4 | if [[ -z "${ROX_ENDPOINT}" ]]; then 5 | echo >&2 "ROX_ENDPOINT must be set" 6 | exit 1 7 | fi 8 | 9 | if [[ -z "${ROX_API_TOKEN}" ]]; then 10 | echo >&2 "ROX_API_TOKEN must be set" 11 | exit 1 12 | fi 13 | 14 | deployment_value=NA 15 | deploymentname_value=NA 16 | namespace_value=NA 17 | clustername_value=NA 18 | clusterid_value=NA 19 | format_value=table 20 | display_node_value="false" 21 | control_plane_value=NA 22 | 23 | process_arg() { 24 | arg=$1 25 | 26 | key="$(echo "$arg" | cut -d "=" -f 1)" 27 | value="$(echo "$arg" | cut -d "=" -f 2)" 28 | 29 | if [[ "$key" == "deployment" ]]; then 30 | deployment_value="$value" 31 | elif [[ "$key" == "deploymentname" ]]; then 32 | deploymentname_value="$value" 33 | elif [[ "$key" == "namespace" ]]; then 34 | namespace_value="$value" 35 | elif [[ "$key" == "clustername" ]]; then 36 | clustername_value="$value" 37 | elif [[ "$key" == "clusterid" ]]; then 38 | clusterid_value="$value" 39 | elif [[ "$key" == "format" ]]; then 40 | format_value="$value" 41 | elif [[ "$key" == "display_node" ]]; then 42 | display_node_value="$value" 43 | elif [[ "$key" == "control_plane" ]]; then 44 | control_plane_value="$value" 45 | fi 46 | } 47 | 48 | process_args() { 49 | for arg in "$@"; do 50 | process_arg "$arg" 51 | done 52 | } 53 | 54 | create_pod_node_map() { 55 | 56 | local pods_response="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/pods" -k --header "Authorization: Bearer $ROX_API_TOKEN")" 57 | 58 | # Parse the JSON response using jq and loop through the pods 59 | for pod in $(echo "$pods_response" | jq -r '.pods[] | @base64'); do 60 | local pod_json="$(echo "$pod" | base64 --decode)" # decode base64 pod JSON 61 | 62 | # Extract pod ID and node from the pod JSON 63 | local pod_id="$(echo "$pod_json" | jq -r '.id')" 64 | pod_id_key="$(echo "$pod_id" | tr -d "-")" 65 | local live_instances="$(echo "$pod_json" | jq -r '.liveInstances')" 66 | if [[ "$live_instances" != "null" ]]; then 67 | node="$(echo "$live_instances" | jq -r '.[0].instanceId.node')" 68 | 69 | # Add pod ID and node to the associative array 70 | pod_node_map["$pod_id_key"]=$node 71 | fi 72 | done 73 | } 74 | 75 | get_deployments() { 76 | if [[ "$deployment_value" == "NA" ]]; then 77 | json_deployments="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/deployments" -k -H "Authorization: Bearer $ROX_API_TOKEN")" 78 | 79 | if [[ "$namespace_value" != "NA" ]]; then 80 | json_deployments="$(echo "$json_deployments" | jq --arg namespace "$namespace_value" '{deployments: [.deployments[] | select(.namespace == $namespace)]}')" 81 | fi 82 | 83 | if [[ "$control_plane_value" == "control_plane_only" ]]; then 84 | json_deployments="$(echo "$json_deployments" | jq '{deployments: [.deployments[] | select(.namespace == "kube-node-lease" or .namespace == "kube-public" or .namespace == "kube-system")]}')" 85 | fi 86 | 87 | if [[ "$control_plane_value" == "without_control_plane" ]]; then 88 | json_deployments="$(echo "$json_deployments" | jq '{deployments: [.deployments[] | select(.namespace != "kube-node-lease" and .namespace != "kube-public" and .namespace != "kube-system")]}')" 89 | fi 90 | 91 | if [[ "$deploymentname_value" != "NA" ]]; then 92 | json_deployments="$(echo "$json_deployments" | jq --arg deploymentname "$deploymentname_value" '{deployments: [.deployments[] | select(.name == $deploymentname)]}')" 93 | fi 94 | 95 | if [[ "$clustername_value" != "NA" ]]; then 96 | json_deployments="$(echo "$json_deployments" | jq --arg clustername "$clustername_value" '{deployments: [.deployments[] | select(.cluster == $clustername)]}')" 97 | fi 98 | 99 | if [[ "$clusterid_value" != "NA" ]]; then 100 | json_deployments="$(echo "$json_deployments" | jq --arg clusterid "$clusterid_value" '{deployments: [.deployments[] | select(.clusterId == $clusterid)]}')" 101 | fi 102 | 103 | deployments=($(echo "$json_deployments" | jq -r '.deployments[].id')) 104 | else 105 | deployments=($deployment_value) 106 | fi 107 | } 108 | 109 | get_node() { 110 | local listening_endpoint="$1" 111 | 112 | podUid="$(echo $listening_endpoint | jq -r .podUid)" 113 | if [[ -n "$podUid" ]]; then 114 | pod_id_key="$(echo "$podUid" | tr -d "-")" 115 | node=${pod_node_map[$pod_id_key]} 116 | else 117 | node="" 118 | fi 119 | 120 | echo "$node" 121 | } 122 | 123 | get_listening_endpoints_for_json() { 124 | for deployment in ${deployments[@]}; do 125 | listening_endpoints="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/listening_endpoints/deployment/$deployment" -k --header "Authorization: Bearer $ROX_API_TOKEN")" || true 126 | if [[ "$listening_endpoints" != "" ]]; then 127 | nlistening_endpoints="$(echo $listening_endpoints | jq '.listeningEndpoints | length')" 128 | if [[ "$nlistening_endpoints" > 0 ]]; then 129 | if [[ "$display_node_value" == "true" ]]; then 130 | for ((j = 0; j < nlistening_endpoints; j = j + 1)); do 131 | listening_endpoint="$(echo $listening_endpoints | jq -r .listeningEndpoints[$j])" 132 | node="$(get_node "$listening_endpoint")" 133 | listening_endpoints="$(echo "$listening_endpoints" | jq ".listeningEndpoints[$j].node = \"$node\"")" 134 | done 135 | fi 136 | echo "deployment= $deployment" 137 | echo $listening_endpoints | jq 138 | echo 139 | fi 140 | fi 141 | done 142 | } 143 | 144 | get_listening_endpoints_for_table() { 145 | table_lines="" 146 | 147 | for deployment in ${deployments[@]}; do 148 | listening_endpoints="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/listening_endpoints/deployment/$deployment" -k --header "Authorization: Bearer $ROX_API_TOKEN")" || true 149 | if [[ "$listening_endpoints" != "" ]]; then 150 | nlistening_endpoints="$(echo $listening_endpoints | jq '.listeningEndpoints | length')" 151 | 152 | for ((j = 0; j < nlistening_endpoints; j = j + 1)); do 153 | l4_proto="$(echo $listening_endpoints | jq -r .listeningEndpoints[$j].endpoint.protocol)" 154 | if [[ "$l4_proto" == L4_PROTOCOL_TCP ]]; then 155 | proto=tcp 156 | elif [[ "$l4_proto" == L4_PROTOCOL_UDP ]]; then 157 | proto=udp 158 | else 159 | proto=unkown 160 | fi 161 | 162 | listening_endpoint="$(echo $listening_endpoints | jq -r .listeningEndpoints[$j])" 163 | name="$(echo $listening_endpoint | jq -r .signal.name)" 164 | exec_file_path="$(echo $listening_endpoint | jq -r .signal.execFilePath)" 165 | plop_port="$(echo $listening_endpoint | jq -r .endpoint.port)" 166 | namespace="$(echo $listening_endpoint | jq -r .namespace)" 167 | clusterId="$(echo $listening_endpoint | jq -r .clusterId)" 168 | podId="$(echo $listening_endpoint | jq -r .podId)" 169 | containerName="$(echo $listening_endpoint | jq -r .containerName)" 170 | pid="$(echo $listening_endpoint | jq -r .signal.pid)" 171 | 172 | table_line=$(printf "%-40s %-9s %-7s %-7s %-15s %-40s %-55s %-20s" \ 173 | "$exec_file_path" "$pid" "$plop_port" "$proto" "$namespace" "$clusterId" \ 174 | "$podId" "$containerName") 175 | 176 | if [[ "$display_node_value" == "true" ]]; then 177 | node="$(get_node "$listening_endpoint")" 178 | table_line="${table_line} $node" 179 | fi 180 | 181 | 182 | table_lines="${table_lines}${table_line}\n" 183 | done 184 | fi 185 | done 186 | 187 | echo 188 | header=$(printf "%-40s %-9s %-7s %-7s %-15s %-40s %-55s %-20s" \ 189 | "Exec file path" "PID" "Port" "Proto" "Namespace" "clusterId" \ 190 | "podId" "containerName") 191 | 192 | 193 | if [[ "$display_node_value" == "true" ]]; then 194 | header="${header} node" 195 | fi 196 | 197 | echo -e "$header" 198 | echo -e "$table_lines" 199 | 200 | } 201 | 202 | process_args $@ 203 | 204 | deployments=() 205 | get_deployments 206 | declare -A pod_node_map # associative array to store pod ID as key and node as value 207 | create_pod_node_map 208 | if [[ "$format_value" == "json" ]]; then 209 | get_listening_endpoints_for_json 210 | else 211 | get_listening_endpoints_for_table 212 | fi 213 | 214 | -------------------------------------------------------------------------------- /util-scripts/log4shell/README.md: -------------------------------------------------------------------------------- 1 | # log4shell mitigation checker 2 | 3 | This Python 3 script uses the StackRox API to find all deployments affected by CVE-2021-44228 and check whether environment variable based mitigations have been applied 4 | 5 | **Required Environment Vars:** 6 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 7 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/3.67/integration/integrate-with-ci-systems.html#cli-authentication_integrate-with-ci-systems) 8 | 9 | 10 | **Usage** 11 | `python3 log4shell-check.py > /tmp/output.csv` -------------------------------------------------------------------------------- /util-scripts/log4shell/log4shell-check.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import os 4 | import pandas as pd 5 | import urllib3 6 | 7 | # check that required env variables are present 8 | if "ROX_ENDPOINT" not in os.environ or "ROX_API_TOKEN" not in os.environ: 9 | print("ROX_ENDPOINT and ROX_API_TOKEN must be set") 10 | quit() 11 | 12 | # disabling TLS certificate check 13 | # for self-signed installs 14 | urllib3.disable_warnings() 15 | 16 | # properly formats ROX_API_TOKEN 17 | # for requests 18 | class BearerAuth(requests.auth.AuthBase): 19 | def __init__(self, token): 20 | self.token = token 21 | def __call__(self, r): 22 | r.headers["authorization"] = "Bearer " + self.token 23 | return r 24 | 25 | rox_endpoint = os.getenv('ROX_ENDPOINT') 26 | rox_api_token = os.getenv('ROX_API_TOKEN') 27 | 28 | results = {} 29 | 30 | # Start by getting all deployments impacted by CVE-2021-44228 31 | try: 32 | affected_deployments = requests.get('https://' + rox_endpoint + '/v1/deploymentswithprocessinfo?query=CVE%3ACVE-2021-44228', auth=BearerAuth(rox_api_token), verify=False).json() 33 | except: 34 | print("Unable to get deployments affected by CVE-2021-44228. Exiting") 35 | quit() 36 | 37 | # Get each individual deployment 38 | for affected_deployment in affected_deployments['deployments']: 39 | try: 40 | deployment_details = requests.get('https://' + rox_endpoint + '/v1/deploymentswithrisk/' + affected_deployment['deployment']['id'], auth=BearerAuth(rox_api_token), verify=False).json()['deployment'] 41 | except: 42 | print('Unable to get details for deployment {0}/{1}/{2}'.format(deployment_details['clusterName'], deployment_details['namespace'], deployment_details['name'])) 43 | break 44 | # And loop through all the container specs in the deployment 45 | # This allows us to return accurate results if only one container spec is affected/unaffected 46 | for containerspec in deployment_details['containers']: 47 | try: 48 | vulns = requests.get('https://' + rox_endpoint + '/v1/images/' + containerspec['image']['id'], auth=BearerAuth(rox_api_token), verify=False).json()['scan']['components'] 49 | except: 50 | print('Unable to get vulnerabilities for container spec {3} in deployment {0}/{1}/{2}'.format(deployment_details['clusterName'], deployment_details['namespace'], deployment_details['name'], containerspec["name"])) 51 | break 52 | vuln_dataframe = pd.DataFrame(vulns) 53 | log4j_dataframe = vuln_dataframe[(vuln_dataframe["name"]=="log4j")] 54 | if log4j_dataframe.count(axis=0)["name"] > 0: 55 | mitigation_present = 'false' 56 | for container in deployment_details['containers']: 57 | # Check each of the two mitigation strategies 58 | for envvar in container['config']['env']: 59 | if envvar['key'] == "LOG4J_FORMAT_MSG_NO_LOOKUPS" and envvar['value'] == "true": 60 | mitigation_present = 'true' 61 | elif envvar['key'] == "JAVA_TOOL_OPTIONS" and "-Dlog4j2.formatMsgNoLookups=true" in envvar['value']: 62 | mitigation_present = 'true' 63 | # Create a deployment dict and add it to our set of results 64 | deployment = { 'cluster': deployment_details['clusterName'], 'namespace': deployment_details['namespace'], 'name': deployment_details['name'], 'container': containerspec["name"], 'image': containerspec['image']['name']['fullName'], 'mitigation': mitigation_present } 65 | results[containerspec['id']] = deployment 66 | 67 | # Create a DataFrame and return it as CSV 68 | results_table = pd.DataFrame.from_dict(data=results, orient='index') 69 | print(results_table.to_csv(index=False)) 70 | 71 | -------------------------------------------------------------------------------- /util-scripts/log4shell/requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | requests -------------------------------------------------------------------------------- /util-scripts/policy-copy-all/README.md: -------------------------------------------------------------------------------- 1 | # Copy All StackRox Policies 2 | 3 | This script copies all the security policies adding the suffix `(COPY)` and excluding common OpenShift namespaces. 4 | 5 | **Required Environment Vars:** 6 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 7 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/3.74/cli/getting-started-cli.html#cli-authentication_cli-getting-started) 8 | 9 | **Required Tools:** 10 | * `jq` is used by this script and must be installed. Installation instructions for various platforms can be found [here](https://stedolan.github.io/jq/download/) 11 | 12 | **Usage** 13 | `./policy-copy-all.sh` 14 | 15 | > NOTE: For additional exclusions, modify or append an object to the `exclude_ns` value. 16 | 17 | -------------------------------------------------------------------------------- /util-scripts/policy-copy-all/policy-copy-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Purpose: clone all the system policies adding the system namespaces to scope exclusions 4 | # Requires: 5 | # - curl, jq 6 | # - $ROX_API_TOKEN = contains an API token with the following permissions: 7 | # Policy (read) - for example a token with the StackRox 'Analyst' role 8 | # - $ROX_ENDPOINT = hostname/address of StackRox Central (:port if not 443) 9 | # - Update the exclude_ns variable to add/remove namespaces from the exclusion 10 | # Actions: 11 | # - Extract all system policies 12 | # - Copy each policy 13 | # - Add suffix "(COPY)" to each copied policy 14 | # - Add system namespaces to scope exclusions in each policy 15 | # - Create a new policy from the copy 16 | # - Update the original policy to disable it 17 | 18 | if [[ -z "${ROX_ENDPOINT}" ]]; then 19 | echo >&2 "Environment variable ROX_ENDPOINT must be set" 20 | echo >&2 "export ROX_ENDPOINT=stackrox.example.com" 21 | exit 1 22 | fi 23 | 24 | if [[ -z "${ROX_API_TOKEN}" ]]; then 25 | echo >&2 "Environment variable ROX_API_TOKEN must be set" 26 | echo >&2 "export ROX_API_TOKEN=$(cat token-file)" 27 | exit 1 28 | fi 29 | 30 | if ! [[ -x "$(command -v curl)" ]]; then 31 | echo >&2 "curl does not exist or is not in the PATH" 32 | exit 1 33 | fi 34 | 35 | if ! [[ -x "$(command -v jq)" ]]; then 36 | echo >&2 "jq does not exist or is not in the PATH" 37 | exit 1 38 | fi 39 | 40 | # Definition of the system namespaces to be excluded 41 | # Add another object to include additional namespaces 42 | exclude_ns='{ 43 | "name": "Do not alert on system namespaces", 44 | "deployment": { 45 | "name": "", 46 | "scope": { 47 | "cluster": "", 48 | "namespace": "^kube.*|^openshift.*|^redhat.*|^istio.*|^rhacs-operator.*|^stackrox.*", 49 | "label": null 50 | } 51 | }, 52 | "image": null, 53 | "expiration": null 54 | }' 55 | 56 | ## Optional system namespaces in OpenShift 57 | #{ 58 | # "name": "Do not alert on system namespaces", 59 | # "deployment": { 60 | # "name": "", 61 | # "scope": { 62 | # "cluster": "", 63 | # "namespace": "^3scale$|^amq$|^dm$|^hive$|^local-cluster$|^open-cluster-management.*", 64 | # "label": null 65 | # } 66 | # }, 67 | # "image": null, 68 | # "expiration": null 69 | #}' 70 | 71 | 72 | # Suffix to be added to the cloned policy 73 | policy_name_suffix='(COPY)' 74 | 75 | # Function to interact with the API using curl 76 | function curl_central() { 77 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/$1" 78 | } 79 | 80 | function curl_get_policy_details() { 81 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/v1/policies/$1" 82 | } 83 | 84 | function curl_post_policy() { 85 | curl -sk -XPOST -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/v1/policies" --data "$1" 86 | 87 | } 88 | 89 | function curl_put_policy() { 90 | curl -sk -XPUT -H "Authorization: Bearer ${ROX_API_TOKEN}" \ 91 | "https://${ROX_ENDPOINT}/v1/policies/$1" --data "$2" 92 | } 93 | 94 | policies_output=0 95 | policies=$(curl_central "v1/policies" | jq -r '.policies[] | .id') 96 | for policy_id in $policies 97 | do 98 | echo 99 | echo "Processing Policy: $policy_id" 100 | echo "======================================================" 101 | 102 | current_policy=$(curl_get_policy_details "${policy_id}" | jq 'del(. | .id, .lastUpdated, .policyVersion, .SORTName, .SORTLifecycleStage)' ) 103 | 104 | policy_name=$( echo $current_policy | jq -r '.name' ) 105 | 106 | echo "$policy_name | grep $policy_name_suffix" 107 | 108 | if echo $policy_name | grep "$policy_name_suffix" &>/dev/null 109 | then 110 | continue 111 | fi 112 | 113 | # Set current policy to disabled 114 | updated_policy=$( echo $current_policy | jq ".disabled = true" ) 115 | # Add suffix to the name of the clone and the exclusions 116 | new_policy=$( echo $current_policy | jq -c ".exclusions += [$exclude_ns] | .name = \"$policy_name $policy_name_suffix\"" ) 117 | 118 | echo "Creating a new policy from the copy" 119 | res=$( curl_post_policy "$new_policy" ) 120 | if echo "$res" | grep error &>/dev/null 121 | then 122 | echo "Creation error: $res" 123 | continue 124 | else 125 | echo "Disable the current policy" 126 | res=$(curl_put_policy $policy_id "$updated_policy") 127 | if [ "$res" == "{}" ] 128 | then 129 | policies_output=$((policies_output + 1)) 130 | else 131 | echo "Update error: $res" 132 | fi 133 | fi 134 | 135 | done 136 | 137 | echo 138 | echo "$policies_output policies were cloned." 139 | 140 | -------------------------------------------------------------------------------- /util-scripts/policy-update/README.md: -------------------------------------------------------------------------------- 1 | # Sync StackRox Policy 2 | 3 | This script consumes a policy definition json file and either creates or updates and existing StackRox policy of the same name if there are any changes 4 | 5 | **Required Environment Vars:** 6 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 7 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/3.74/cli/getting-started-cli.html#cli-authentication_cli-getting-started) 8 | 9 | **Required Argument:** 10 | * `$1 = path/to/policy_definition.json` 11 | 12 | **Required Tools:** 13 | * `jq` is used by this script and must be installed. Installation instructions for various platforms can be found [here](https://stedolan.github.io/jq/download/) 14 | 15 | **Usage** 16 | `./policy-update.sh /policies/root-user.json` 17 | 18 | -------------------------------------------------------------------------------- /util-scripts/policy-update/policy-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage, ./policy-update.sh policy.json 3 | 4 | if [[ -z "${ROX_ENDPOINT}" ]]; then 5 | echo >&2 "ROX_ENDPOINT must be set" 6 | exit 1 7 | fi 8 | 9 | if [[ -z "${ROX_API_TOKEN}" ]]; then 10 | echo >&2 "ROX_API_TOKEN must be set" 11 | exit 1 12 | fi 13 | 14 | if [[ -z "$1" ]]; then 15 | echo >&2 "usage: policy-update.sh " 16 | exit 1 17 | fi 18 | 19 | input_file_path="$1" 20 | 21 | input=$(cat $PWD/$1) 22 | 23 | echo $input | jq type 24 | RES=$? 25 | if [ $RES != 0 ]; then 26 | exit 1 27 | fi 28 | 29 | input_data() 30 | { 31 | cat <&2 "Environment variable ROX_ENDPOINT must be set" 11 | echo >&2 "export ROX_ENDPOINT=stackrox.example.com" 12 | exit 1 13 | fi 14 | 15 | if [[ -z "${ROX_API_TOKEN}" ]]; then 16 | echo >&2 "Environment variable ROX_API_TOKEN must be set" 17 | echo >&2 "export ROX_API_TOKEN=$(cat token-file)" 18 | exit 1 19 | fi 20 | 21 | if ! [[ -x "$(command -v curl)" ]]; then 22 | echo >&2 "curl does not exist or is not in the PATH" 23 | exit 1 24 | fi 25 | 26 | if ! [[ -x "$(command -v jq)" ]]; then 27 | echo >&2 "jq does not exist or is not in the PATH" 28 | exit 1 29 | fi 30 | 31 | # Name of output file 32 | output_file="policies.csv" 33 | if [[ -f "$output_file" ]]; then 34 | rm "$output_file" 35 | fi 36 | echo "Name,Description,LifecycleStages,Severity,Disabled,Rationale,Remediation,Categories,Notifiers,ID" > "$output_file" 37 | 38 | function curl_central() { 39 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/$1" 40 | } 41 | 42 | policies_output=0 43 | 44 | policies=$(curl_central "v1/policies" | jq -r '.policies[] | .id') 45 | for policy_id in $policies 46 | do 47 | #echo "$policy_id" 48 | policy_details=$(curl_central "v1/policies/$policy_id") 49 | 50 | #echo "$policy_details" 51 | echo "$policy_details" | jq -r '{name: .name, description: .description, lifecycleStages: (.lifecycleStages | join("|")), 52 | severity: .severity, disabled: .disabled, rationale: .rationale, remediation: .remediation, categories: (.categories | join("|")), 53 | notifiers: (.notifiers | join("|")), id: .id} 54 | | [.name, .description, .lifecycleStages, .severity, .disabled, .rationale, .remediation, .categories, .notifiers, .id] 55 | | @csv' >> "$output_file" 56 | 57 | policies_output=$((policies_output + 1)) 58 | done 59 | 60 | echo "$policies_output policies were written to $output_file." 61 | -------------------------------------------------------------------------------- /util-scripts/rhcos-node-cves/README.md: -------------------------------------------------------------------------------- 1 | # Desription 2 | 3 | This script exports all Red Hat CoreOs (RHCOS) Node CVES from an Openshift cluster with Red Hat Advanced Cluster Security installed. 4 | 5 | ## Required Environment Vars 6 | 7 | ROX_ENDPOINT - Host for StackRox central (central.example.com) 8 | 9 | ROX_API_TOKEN - Token data from StackRox API token [How to generate an API Token](https://docs.openshift.com/acs/4.4/configuration/configure-api-token.html) 10 | 11 | ## Usage 12 | 13 | Run the script ./node-cve-report.sh to generate a results.json file with all CVE information 14 | 15 | Optional - Save to csv (i.e. ./node-cve-report.sh > report.csv) 16 | 17 | Important: Red Hat ACS only support Node CVE scanning for RHCOS. It is not designed for non-openshift environments running Kubernetes. See [Documentation](https://docs.openshift.com/acs/4.4/operating/manage-vulnerabilities/scan-rhcos-node-host.html) for more details. 18 | -------------------------------------------------------------------------------- /util-scripts/rhcos-node-cves/node-cve-report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script pulls out a report on CVEs affecting all images in one or all namespaces 3 | # Requires ROX_ENDPOINT and ROX_API_TOKEN environment variables 4 | # Requires analyst access or more in ACS 5 | # Usage: $0 name-of-namespace-to-scan | nothing-which-scans-all-namespaces 6 | # Magnus Glantz, sudo@redhat.com, 2021, with great help from Neil Carpenter 7 | # and inspiration from https://github.com/stackrox/contributions/blob/main/util-scripts/violations-to-csv/violations-to-csv.sh 8 | 9 | 10 | #! /bin/bash 11 | # This script builds a CSV file from for the active deployments on the violations page. 12 | # Requires ROX_ENDPOINT and ROX_API_TOKEN environment variables 13 | ################################ 14 | 15 | 16 | case $1 in 17 | *help) 18 | echo "$0 cluster-name|nothing-to-scan-all-namespaces" 19 | ;; 20 | esac 21 | 22 | if [[ -z "${ROX_ENDPOINT}" ]]; then 23 | echo >&2 "ROX_ENDPOINT must be set" 24 | exit 1 25 | fi 26 | 27 | if [[ -z "${ROX_API_TOKEN}" ]]; then 28 | echo >&2 "ROX_API_TOKEN must be set" 29 | exit 1 30 | fi 31 | 32 | if [[ -z "${1}" ]]; then 33 | all_clusters=1 34 | else 35 | all_clusters=0 36 | cluster=$1 37 | fi 38 | 39 | # create the output file 40 | echo '["CVE", "CVSS Score", "Summary", "Link", "Component", "Version"], (.scan.components | .[] | . as $component | .vulnerabilities[] | [.cveBaseInfo.cve, .cvss, .cveBaseInfo.summary, .cveBaseInfo.link, $component.name, $component.version]) | @csv' > nodes_query 41 | 42 | function curl_central() { 43 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/$1" 44 | } 45 | 46 | # Gather CVEs for nodes in all clusters 47 | 48 | if [ -f results.json ]; then 49 | rm -f results.json 50 | fi 51 | 52 | if [[ "$all_clusters" -eq 1 ]]; then 53 | for clusterid in $(curl_central "v1/clusters" | jq -r ".clusters[].id") 54 | do 55 | for nodeid in $(curl_central "v1/nodes/${clusterid}"|jq -r ".nodes[].id") 56 | do 57 | curl_central v1/nodes/${clusterid}/${nodeid}|jq >>results.json 58 | done 59 | done 60 | elif [[ "$all_clusters" -eq 0 ]]; then 61 | for nodeid in $(curl_central "v1/nodes/${clusterid}"|jq -r ".nodes[].id") 62 | do 63 | echo "We are in $clusterid looking at $nodeid" 64 | curl_central v1/nodes/${clusterid}/{$nodeid}|jq >>results.json 65 | done 66 | fi 67 | 68 | jq -r -f nodes_query results.json 69 | -------------------------------------------------------------------------------- /util-scripts/roxctl-base-image/README.md: -------------------------------------------------------------------------------- 1 | Parses results from "roxctl image scan" to find the lowest-numbered ("base") layer and determine its age 2 | 3 | Output provides age of base image, in days 4 | 5 | Usage: 6 | 7 | `roxctl image scan -e $CENTRAL:443 --image quay.io/example:1.0 | ./base.py 8 | ` 9 | 10 | Notes: 11 | - Also works with the JSON returned from GET /v1/images/{id} in ACS API. 12 | -------------------------------------------------------------------------------- /util-scripts/roxctl-base-image/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import json 5 | from datetime import datetime, timedelta 6 | 7 | currentTime=datetime.now() 8 | 9 | image_json = json.load(sys.stdin) 10 | 11 | returnCode=0 12 | 13 | try: 14 | if created := image_json["metadata"]["v1"]["layers"][0]["created"]: 15 | print ("created: ", created) 16 | createdDate = datetime.strptime(created[0:10], "%Y-%m-%d") 17 | timeDiff = currentTime - createdDate 18 | print ("base image is", timeDiff.days, "days old") 19 | except KeyError: 20 | print ("base image layer not found") 21 | pass 22 | 23 | exit(returnCode) 24 | -------------------------------------------------------------------------------- /util-scripts/roxctl-grace-period/README.md: -------------------------------------------------------------------------------- 1 | Parses results from "roxctl image scan" to determine which fixable CVEs fall into a defined "Grace Period" in days. Default grace period is 30 days. 2 | 3 | Output lists each Fixable CVE as 4 | - "in grace": CVE Published less than "grace period" days ago 5 | - "out of grace": CVE Published more than "grace period" days ago 6 | 7 | Usage: 8 | 9 | `roxctl image scan -e $CENTRAL:443 --image quay.io/example:1.0 | ./grace.py 10 | ` 11 | 12 | Notes: 13 | - Also works with the JSON returned from GET /v1/images/{id} in ACS API. 14 | -------------------------------------------------------------------------------- /util-scripts/roxctl-grace-period/grace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import json 5 | from datetime import datetime, timedelta 6 | 7 | gracePeriod=30 8 | currentTime=datetime.now() 9 | 10 | image_json = json.load(sys.stdin) 11 | 12 | returnCode=0 13 | 14 | for component in image_json["scan"]["components"]: 15 | try: 16 | for vuln in component["vulns"]: 17 | try: 18 | if vuln["fixedBy"]: 19 | pubTimestamp = datetime.strptime(vuln["publishedOn"][0:10], "%Y-%m-%d") 20 | if (currentTime - timedelta(days=gracePeriod)) > pubTimestamp: 21 | print ("out of grace: ", component["name"], vuln["cve"], vuln["severity"], vuln["fixedBy"], vuln["publishedOn"]) 22 | returnCode=1 23 | else: 24 | print ("in grace: ", component["name"], vuln["cve"], vuln["severity"], vuln["fixedBy"], vuln["publishedOn"]) 25 | except KeyError: 26 | pass 27 | except KeyError: 28 | pass 29 | 30 | exit(returnCode) 31 | -------------------------------------------------------------------------------- /util-scripts/scan-all-registry-images/README.md: -------------------------------------------------------------------------------- 1 | # Scan All Images in an ECR Registry using roxctl 2 | 3 | This script uses roxctl to scan all images in all ecr image registries within an AWS region.Note: The images scanned in these registries will appear as 'inactive' in the GUI for ACS unless currently running within a deployment in the runtime environment. Scan results for vulnerabilities can still be analyzed from the UI. 4 | 5 | **Prereqs** 6 | * The aws cli must be installed and configured for use 7 | * A secret containing the value of the API token for Central must be created in AWS secrets manager and referenced in the environment variable (see below) 8 | 9 | **Required Environment Vars** 10 | * `ROX_CENTRAL_ADDRESS` - Host for StackRox central (central.example.com) 11 | * `ROX_SECRET_TOKEN_LOCATION` - Location of secret value in AWS Secrets Manager containing the ROX_API_TOKEN string (see https://docs.openshift.com/acs/3.67/cli/getting-started-cli.html#cli-authentication_cli-getting-started for more detail on generating a token) 12 | * `AWS_REGION` - AWS region containing the ecr repositories you would like to scan 13 | 14 | **Usage** 15 | `./ecr-scan-roxctl.sh` 16 | 17 | -------------------------------------------------------------------------------- /util-scripts/scan-all-registry-images/ecr-scan-roxctl.sh: -------------------------------------------------------------------------------- 1 | # hardcoded vars 2 | ROX_CENTRAL_ADDRESS="" 3 | ROX_SECRET_TOKEN_LOCATION="" 4 | AWS_REGION="us-west-2" 5 | 6 | # Dynamic Stuff 7 | REPOSITORIES=$(aws ecr describe-repositories --region $AWS_REGION | jq -c '.repositories') 8 | 9 | # Auth Stuff 10 | export ROX_API_TOKEN=$(aws secretsmanager get-secret-value --region us-west-2 --secret-id $ROX_SECRET_TOKEN_LOCATION | jq -r .SecretString) 11 | 12 | echo ${REPOSITORIES} | jq -c '.[]' | while read REPOSITORY 13 | do 14 | REGISTRY_ID=$(echo ${REPOSITORY} | jq -r '.registryId') 15 | REPO_NAME=$(echo ${REPOSITORY} | jq -r '.repositoryName') 16 | BASE_IMAGE_URI=$(echo ${REPOSITORY} | jq -r '.repositoryUri') 17 | 18 | for IMAGE_TAG in $(aws ecr list-images --region $AWS_REGION --registry-id $REGISTRY_ID --repository-name $REPO_NAME | jq -r '.imageIds[].imageTag') 19 | do 20 | IMAGE_URI="${BASE_IMAGE_URI}:${IMAGE_TAG}" 21 | roxctl -e $ROX_CENTRAL_ADDRESS image check --image=$IMAGE_URI 22 | done 23 | done 24 | -------------------------------------------------------------------------------- /util-scripts/violations-to-csv/README.md: -------------------------------------------------------------------------------- 1 | # Violations to CSV 2 | 3 | This script uses the StackRox API to export all of the active deployments on the violations page to a CSV file. 4 | 5 | **Required Environment Vars:** 6 | * `ROX_ENDPOINT` - Host for StackRox central (central.example.com) 7 | * `ROX_API_TOKEN` - Token data from [StackRox API token](https://docs.openshift.com/acs/3.74/cli/getting-started-cli.html#cli-authentication_cli-getting-started) 8 | 9 | **Required Argument:** 10 | * `$1 = path/to/output_file.csv` 11 | 12 | **Usage** 13 | `./violations-to-csv.sh /tmp/output.csv` 14 | 15 | -------------------------------------------------------------------------------- /util-scripts/violations-to-csv/violations-to-csv.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # This script builds a CSV file from for the active deployments on the violations page. 3 | # Requires ROX_ENDPOINT and ROX_API_TOKEN environment variables 4 | 5 | if [[ -z "${ROX_ENDPOINT}" ]]; then 6 | echo >&2 "ROX_ENDPOINT must be set" 7 | exit 1 8 | fi 9 | 10 | if [[ -z "${ROX_API_TOKEN}" ]]; then 11 | echo >&2 "ROX_API_TOKEN must be set" 12 | exit 1 13 | fi 14 | 15 | if [[ -z "$1" ]]; then 16 | echo >&2 "usage: create-csv.sh " 17 | exit 1 18 | fi 19 | 20 | output_file="$1" 21 | echo '"Policy", "Description", "Severity", "Cluster", "Namespace", "Resource Type", "Resource Name", "Time", "Enforcement Count", "Enforcement Action"' > "${output_file}" 22 | 23 | function curl_central() { 24 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}/$1" 25 | } 26 | 27 | # Collect all alerts 28 | 29 | res="$(curl_central "v1/alerts?query=Inactive%20Deployment%3Afalse")" 30 | echo $res 31 | 32 | # If no results, then exist 33 | if [[ "$(echo "${res}" | jq '.alerts | length')" == "0" ]]; then 34 | break 35 | fi 36 | 37 | # Iterate over all alerts 38 | 39 | echo $res | jq -c -r '.alerts[]' | while IFS= read alert; do 40 | # Format the CSV correctly 41 | echo "=====" 42 | echo $alert 43 | echo "=====" 44 | echo "${alert}" | jq -r '[.policy.name, .policy.description, .policy.severity, .commonEntityInfo.clusterName, .commonEntityInfo.namespace, .commonEntityInfo.resourceType, (if (.commonEntityInfo.resourceType == "DEPLOYMENT") then .deployment.name else .resource.name end), .time, .enforcementCount, .enforcementAction] | @csv' >> "${output_file}" 45 | done 46 | -------------------------------------------------------------------------------- /util-scripts/vuln-violation-details/vulnvdetails.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Simple example to retrieve all Violations ("alerts") from StackRox Central for the default policy "Fixable Severity at least Important" 4 | # 5 | # Requires 'curl' and 'jq' 6 | # 7 | 8 | # ROX_ENDPOINT is the IP or hostname of your ACS Central 9 | if [[ -z "${ROX_ENDPOINT}" ]]; then 10 | echo >&2 "ROX_ENDPOINT must be set" 11 | exit 1 12 | fi 13 | 14 | # ROX_API_TOKEN is the contents of a StackRox API token created from the UI: 15 | # Platform Configuration -> Integrations 16 | # The token needs to have RBAC permissions that, at a minimum, allow Read Access to "Alert" 17 | if [[ -z "${ROX_API_TOKEN}" ]]; then 18 | echo >&2 "ROX_API_TOKEN must be set" 19 | exit 1 20 | fi 21 | 22 | # Retrieve the "slim" list version of alerts. This list does not have violation details like CVEs 23 | # This can be filtered for any policy, system or user. Here we are only looking for violations 24 | # of the "Fixable Severity at least Important" system policy 25 | ALERTLIST=$( curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}:443/v1/alerts?query=Policy%3AFixable%20Severity%20at%20least%20Important" | jq -r '.alerts.[].id' ) 26 | 27 | # loop through the alerts, by id, to retrieve details of the policy violation 28 | for ALERTID in ${ALERTLIST}; do 29 | ALERTDETAILS=$( curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" "https://${ROX_ENDPOINT}:443/v1/alerts/${ALERTID}" ) 30 | 31 | echo "${ALERTDETAILS}" 32 | echo "" 33 | done 34 | -------------------------------------------------------------------------------- /vulnerability-management/export-workloads/README.md: -------------------------------------------------------------------------------- 1 | # Export workload vulnerabilities via shell script 2 | 3 | The `/v1/export/vuln-mgmt/workloads` API exports workload vulnerabilities in the form 4 | of deployments and their associated images including image vulnerabilities. 5 | 6 | The following sections provide use case examples utilizing either the shell or Python. 7 | 8 | ## Shell script 9 | 10 | See `export-workloads.sh` for a shell script example based on `curl` and `jq`. 11 | 12 | ### Export all workloads 13 | ```shell 14 | export-workloads.sh 15 | ``` 16 | 17 | ### Export all workloads from the cluster `prod` 18 | ```shell 19 | export-workloads.sh "Cluster%3Aprod" 20 | ``` 21 | 22 | ### Export all workloads matching the query `Deployment:app Namespace:default` 23 | ```shell 24 | export-workloads.sh "Deployment%3Aapp%2BNamespace%3Adefault" 25 | ``` 26 | 27 | ### Export all workloads with a timeout of 60 seconds 28 | ```shell 29 | export-workloads.sh "" 60 30 | ``` 31 | 32 | ## Python script 33 | 34 | See `export-workloads.py` for a python script example. 35 | 36 | ### Export all workloads 37 | ```shell 38 | export-workloads.py 39 | ``` 40 | 41 | ### Export all workloads from the cluster `prod` 42 | ```shell 43 | export-workloads.py --query="Cluster%3Aprod" 44 | ``` 45 | 46 | ### Export all workloads matching the query `Deployment:app Namespace:default` 47 | ```shell 48 | export-workloads.py --query="Deployment%3Aapp%2BNamespace%3Adefault" 49 | ``` 50 | 51 | ### Export all workloads with a timeout of 60 seconds 52 | ```shell 53 | export-workloads.py --timeout=60 54 | ``` 55 | -------------------------------------------------------------------------------- /vulnerability-management/export-workloads/export-workloads.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This script pulls workload vulnerabilities in the form of deployments and their 4 | # associated images including image vulnerabilities. 5 | # 6 | # The output is streamed to STDOUT as a series of Python objects with the schema 7 | # 8 | # {"result": {"deployment": {...}, "images": [...]}} 9 | # ... 10 | # {"result": {"deployment": {...}, "images": [...]}} 11 | # 12 | # Further processing may be done on the parsed objects. 13 | # 14 | # Requires ROX_ENDPOINT and ROX_API_TOKEN environment variables. 15 | # The API token requires read access on images and deployments. 16 | 17 | import argparse 18 | import json 19 | import os 20 | import requests 21 | 22 | parser = argparse.ArgumentParser("export-workloads") 23 | parser.add_argument("--query", help="query to filter the deployments (default \"\")", default="") 24 | parser.add_argument("--timeout", help="timeout in seconds (default 0 = no timeout)", default=0, type=int) 25 | args = parser.parse_args() 26 | 27 | endpoint = os.environ["ROX_ENDPOINT"].removeprefix("https://") 28 | url = f"https://{endpoint}/v1/export/vuln-mgmt/workloads" 29 | parameters = f"query={args.query}&timeout={args.timeout}" 30 | headers = {"Authorization": f"Bearer {os.environ['ROX_API_TOKEN']}"} 31 | 32 | session = requests.Session() 33 | with session.get(f"{url}?{parameters}", headers=headers, stream=True) as resp: 34 | resp.raise_for_status() 35 | for line in resp.iter_lines(): 36 | if line: 37 | # Parse JSON object for further processing. Here we simply print out the content. 38 | obj = json.loads(line) 39 | print(f"{obj}\n") 40 | -------------------------------------------------------------------------------- /vulnerability-management/export-workloads/export-workloads.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script pulls workload vulnerabilities in the form of deployments and their 4 | # associated images including image vulnerabilities. 5 | # 6 | # The output is streamed to STDOUT as valid JSON with the schema 7 | # 8 | # [ 9 | # {"result": {"deployment": {...}, "images": [...]}}, 10 | # ... 11 | # {"result": {"deployment": {...}, "images": [...]}} 12 | # ] 13 | # 14 | # Requires ROX_ENDPOINT and ROX_API_TOKEN environment variables. 15 | # The API token requires read access on images and deployments. 16 | 17 | set -euo pipefail 18 | 19 | case $1 in 20 | *help) 21 | echo "$0 [query] [timeout]" 22 | ;; 23 | esac 24 | 25 | if [[ -z "${ROX_ENDPOINT}" ]]; then 26 | echo >&2 "ROX_ENDPOINT must be set" 27 | exit 1 28 | fi 29 | 30 | if [[ -z "${ROX_API_TOKEN}" ]]; then 31 | echo >&2 "ROX_API_TOKEN must be set" 32 | exit 1 33 | fi 34 | 35 | endpoint=https://${ROX_ENDPOINT#https://} 36 | query=$1 37 | timeout=${2:-0} 38 | 39 | curl -sk -H "Authorization: Bearer ${ROX_API_TOKEN}" \ 40 | "${endpoint}/v1/export/vuln-mgmt/workloads?query=$query&timeout=$timeout" | 41 | # Use `jq -nc --slurp` instead for higher throughput but more memory usage. 42 | jq -nc --stream "[fromstream(inputs)]" 43 | --------------------------------------------------------------------------------