├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── docker-build.yml │ ├── release-notification.yml │ └── smoke-test.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets └── demo │ ├── aws │ ├── aws_recon_sample.json │ └── manifest.txt │ ├── gcp-iam │ ├── full-iam-permissions.json │ └── manifest.txt │ └── gcp │ ├── 1590603998-iam-policy.json │ ├── 1590603998-resource.json │ └── manifest.txt ├── collection ├── gcp-cai-exporter │ ├── .dockerignore │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ ├── app.rb │ ├── asset.rb │ ├── build.sh │ ├── buildlocally.sh │ ├── runlocally.sh │ └── version ├── gcp-iam-exporter │ ├── .dockerignore │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ ├── app.rb │ ├── build.sh │ ├── buildlocally.sh │ ├── iam.rb │ ├── runlocally.sh │ └── version ├── k8s-cai-exporter │ ├── .dockerignore │ ├── Dockerfile │ ├── build.sh │ ├── buildlocally.sh │ ├── cronjob.yaml.sh │ ├── k8s-export.sh │ ├── runlocally.sh │ └── version └── scripts │ ├── cai-export.sh │ └── kube-exporter.sh ├── config └── config.yaml ├── docker ├── .dockerdev │ ├── .bashrc │ ├── .psqlrc │ ├── Aptfile │ ├── Dockerfile │ └── Dockerfile.development ├── .dockerignore ├── .env ├── .irbrc ├── .rspec ├── .rubocop.yml ├── .solargraph.yml ├── .vscode │ └── launch.json ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── app │ ├── channels │ │ └── application_cable │ │ │ ├── channel.rb │ │ │ └── connection.rb │ ├── controllers │ │ ├── api │ │ │ ├── campaign_results_controller.rb │ │ │ ├── campaigns_controller.rb │ │ │ ├── controls_controller.rb │ │ │ ├── jobs_controller.rb │ │ │ ├── organizations_controller.rb │ │ │ ├── profiles_controller.rb │ │ │ ├── results_controller.rb │ │ │ ├── sessions_controller.rb │ │ │ ├── sources_controller.rb │ │ │ └── users_controller.rb │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ └── healthcheck_controller.rb │ ├── jobs │ │ ├── analysis_job.rb │ │ ├── application_job.rb │ │ ├── dummy_results_job.rb │ │ ├── example_job.rb │ │ ├── lib │ │ │ ├── batch_importer.rb │ │ │ ├── config │ │ │ │ └── config_loader.rb │ │ │ ├── db │ │ │ │ └── redisgraph.rb │ │ │ ├── fetcher │ │ │ │ ├── file_fetcher.rb │ │ │ │ └── types │ │ │ │ │ ├── bucket_fetcher.rb │ │ │ │ │ └── local_fetcher.rb │ │ │ ├── loader │ │ │ │ ├── asset │ │ │ │ │ ├── asset_loader.rb │ │ │ │ │ ├── asset_router.rb │ │ │ │ │ ├── graph_db_loader.rb │ │ │ │ │ ├── loaders │ │ │ │ │ │ ├── aws_loader.rb │ │ │ │ │ │ ├── aws_loader │ │ │ │ │ │ │ ├── _resource_loader.rb │ │ │ │ │ │ │ ├── accessanalyzer.rb │ │ │ │ │ │ │ ├── acm.rb │ │ │ │ │ │ │ ├── apigateway.rb │ │ │ │ │ │ │ ├── apigatewayv2.rb │ │ │ │ │ │ │ ├── applicationautoscaling.rb │ │ │ │ │ │ │ ├── athena.rb │ │ │ │ │ │ │ ├── autoscaling.rb │ │ │ │ │ │ │ ├── backup.rb │ │ │ │ │ │ │ ├── cloudformation.rb │ │ │ │ │ │ │ ├── cloudfront.rb │ │ │ │ │ │ │ ├── cloudtrail.rb │ │ │ │ │ │ │ ├── cloudwatch.rb │ │ │ │ │ │ │ ├── cloudwatchlogs.rb │ │ │ │ │ │ │ ├── codebuild.rb │ │ │ │ │ │ │ ├── codepipeline.rb │ │ │ │ │ │ │ ├── configservice.rb │ │ │ │ │ │ │ ├── databasemigrationservice.rb │ │ │ │ │ │ │ ├── directoryservice.rb │ │ │ │ │ │ │ ├── dynamodb.rb │ │ │ │ │ │ │ ├── ec2.rb │ │ │ │ │ │ │ ├── ecr.rb │ │ │ │ │ │ │ ├── efs.rb │ │ │ │ │ │ │ ├── eks.rb │ │ │ │ │ │ │ ├── elasticache.rb │ │ │ │ │ │ │ ├── elasticsearch.rb │ │ │ │ │ │ │ ├── elb.rb │ │ │ │ │ │ │ ├── elbv2.rb │ │ │ │ │ │ │ ├── emr.rb │ │ │ │ │ │ │ ├── esc.rb │ │ │ │ │ │ │ ├── firehose.rb │ │ │ │ │ │ │ ├── guardduty.rb │ │ │ │ │ │ │ ├── iam.rb │ │ │ │ │ │ │ ├── iam │ │ │ │ │ │ │ │ ├── group.rb │ │ │ │ │ │ │ │ ├── policy.rb │ │ │ │ │ │ │ │ ├── role.rb │ │ │ │ │ │ │ │ ├── server_certificate.rb │ │ │ │ │ │ │ │ ├── user.rb │ │ │ │ │ │ │ │ └── virtual_mfa_device.rb │ │ │ │ │ │ │ ├── kafka.rb │ │ │ │ │ │ │ ├── kinesis.rb │ │ │ │ │ │ │ ├── kms.rb │ │ │ │ │ │ │ ├── lambda.rb │ │ │ │ │ │ │ ├── organizations.rb │ │ │ │ │ │ │ ├── rds.rb │ │ │ │ │ │ │ ├── redshift.rb │ │ │ │ │ │ │ ├── route53.rb │ │ │ │ │ │ │ ├── route53domains.rb │ │ │ │ │ │ │ ├── s3.rb │ │ │ │ │ │ │ ├── sagemaker.rb │ │ │ │ │ │ │ ├── secretsmanager.rb │ │ │ │ │ │ │ ├── securityhub.rb │ │ │ │ │ │ │ ├── servicequotas.rb │ │ │ │ │ │ │ ├── ses.rb │ │ │ │ │ │ │ ├── shield.rb │ │ │ │ │ │ │ ├── sns.rb │ │ │ │ │ │ │ ├── sqs.rb │ │ │ │ │ │ │ ├── ssm.rb │ │ │ │ │ │ │ ├── support.rb │ │ │ │ │ │ │ ├── transfer.rb │ │ │ │ │ │ │ ├── workspaces.rb │ │ │ │ │ │ │ └── xray.rb │ │ │ │ │ │ ├── gcp_iam_loader.rb │ │ │ │ │ │ ├── gcp_iam_roles_loader.rb │ │ │ │ │ │ ├── gcp_loader.rb │ │ │ │ │ │ ├── gcp_loader │ │ │ │ │ │ │ ├── gcp_compute_address.rb │ │ │ │ │ │ │ ├── gcp_compute_disk.rb │ │ │ │ │ │ │ ├── gcp_compute_firewall.rb │ │ │ │ │ │ │ ├── gcp_compute_forwardingrule.rb │ │ │ │ │ │ │ ├── gcp_compute_instance.rb │ │ │ │ │ │ │ ├── gcp_compute_instancegroup.rb │ │ │ │ │ │ │ ├── gcp_compute_instancegroupmanager.rb │ │ │ │ │ │ │ ├── gcp_compute_instancetemplate.rb │ │ │ │ │ │ │ ├── gcp_compute_network.rb │ │ │ │ │ │ │ ├── gcp_compute_route.rb │ │ │ │ │ │ │ ├── gcp_compute_subnetwork.rb │ │ │ │ │ │ │ ├── gcp_compute_targetpool.rb │ │ │ │ │ │ │ ├── gcp_container_cluster.rb │ │ │ │ │ │ │ ├── gcp_container_nodepool.rb │ │ │ │ │ │ │ ├── gcp_default.rb │ │ │ │ │ │ │ ├── gcp_dns_managedzone.rb │ │ │ │ │ │ │ ├── gcp_dns_policy.rb │ │ │ │ │ │ │ ├── gcp_iam_serviceaccount.rb │ │ │ │ │ │ │ └── gcp_sqladmin_instance.rb │ │ │ │ │ │ ├── k8s_loader.rb │ │ │ │ │ │ └── k8s_loader │ │ │ │ │ │ │ ├── k8s_clusterrole.rb │ │ │ │ │ │ │ ├── k8s_clusterrolebinding.rb │ │ │ │ │ │ │ ├── k8s_configmap.rb │ │ │ │ │ │ │ ├── k8s_endpoints.rb │ │ │ │ │ │ │ ├── k8s_job.rb │ │ │ │ │ │ │ ├── k8s_limitrange.rb │ │ │ │ │ │ │ ├── k8s_node.rb │ │ │ │ │ │ │ ├── k8s_persistentvolumeclaim.rb │ │ │ │ │ │ │ ├── k8s_pod.rb │ │ │ │ │ │ │ ├── k8s_role.rb │ │ │ │ │ │ │ ├── k8s_rolebinding.rb │ │ │ │ │ │ │ ├── k8s_service.rb │ │ │ │ │ │ │ ├── k8s_serviceaccount.rb │ │ │ │ │ │ │ └── k8s_version.rb │ │ │ │ │ └── utils │ │ │ │ │ │ └── enumerable.rb │ │ │ │ ├── file_loader.rb │ │ │ │ └── misc │ │ │ │ │ └── post_loader.rb │ │ │ └── validator │ │ │ │ ├── cai.rb │ │ │ │ └── schema │ │ │ │ └── cai.json │ │ ├── loader_job.rb │ │ ├── pack_job.rb │ │ ├── runner_job.rb │ │ └── spec │ │ │ ├── formatters │ │ │ └── custom_formatter.rb │ │ │ ├── spec_helper.rb │ │ │ └── support │ │ │ ├── db_helper.rb │ │ │ └── redisgraph_helper.rb │ ├── mailers │ │ └── application_mailer.rb │ ├── models │ │ ├── application_record.rb │ │ ├── campaign.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── control.rb │ │ ├── identity.rb │ │ ├── issue.rb │ │ ├── job.rb │ │ ├── organization.rb │ │ ├── profile.rb │ │ ├── resource.rb │ │ ├── result.rb │ │ ├── role.rb │ │ ├── source.rb │ │ ├── tag.rb │ │ ├── tagging.rb │ │ └── user.rb │ ├── serializers │ │ ├── campaign_controls_serializer.rb │ │ ├── campaign_serializer.rb │ │ ├── campaigns_serializer.rb │ │ ├── control_serializer.rb │ │ ├── controls_serializer.rb │ │ ├── profile_serializer.rb │ │ ├── profiles_serializer.rb │ │ ├── results_serializer.rb │ │ └── tag_serializer.rb │ └── views │ │ └── layouts │ │ ├── mailer.html.erb │ │ └── mailer.text.erb ├── bin │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── spring ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── credentials.yml.enc │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── application_controller_renderer.rb │ │ ├── backtrace_silencers.rb │ │ ├── cors.rb │ │ ├── custom.rb │ │ ├── demo.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── lograge.rb │ │ ├── mime_types.rb │ │ ├── omniauth.rb │ │ ├── rolify.rb │ │ ├── sidekiq.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ ├── en.yml │ │ └── errors.en.yml │ ├── master.key │ ├── puma.rb │ ├── routes.rb │ ├── scheduled-jobs.yml │ ├── sidekiq.yml │ ├── spring.rb │ └── webhooks.yml ├── controls │ └── .keep ├── db │ ├── sample_findings.yml │ ├── sample_profiles.yml │ ├── sample_results.yml │ ├── schema.rb │ └── seeds.rb ├── docker-compose-development.yml ├── docker-compose.yml ├── init.sh ├── lib │ ├── application_error.rb │ └── tasks │ │ ├── .keep │ │ ├── batch_import.rake │ │ ├── control_pack_links.rake │ │ ├── convert_findings.rake │ │ └── generate_findings.rake ├── load_dir │ └── .gitkeep ├── public │ ├── .keep │ ├── 404.html │ ├── img │ │ └── logos │ │ │ ├── opencspm-logo-on-dark.svg │ │ │ ├── opencspm-logo-on-light.svg │ │ │ ├── opencspm-mark-on-light.png │ │ │ └── opencspm-mark-on-light.svg │ └── index.html ├── spec │ ├── factories │ │ ├── campaigns.rb │ │ ├── controls.rb │ │ ├── issues.rb │ │ ├── jobs.rb │ │ ├── profiles.rb │ │ ├── resources.rb │ │ ├── results.rb │ │ ├── sources.rb │ │ ├── taggings.rb │ │ └── tags.rb │ ├── jobs │ │ └── example_job_spec.rb │ ├── models │ │ ├── campaign_spec.rb │ │ ├── control_spec.rb │ │ ├── identity_spec.rb │ │ ├── issue_spec.rb │ │ ├── job_spec.rb │ │ ├── organization_spec.rb │ │ ├── profile_spec.rb │ │ ├── resource_spec.rb │ │ ├── result_spec.rb │ │ ├── role_spec.rb │ │ ├── source_spec.rb │ │ ├── tag_spec.rb │ │ ├── tagging_spec.rb │ │ └── user_spec.rb │ ├── rails_helper.rb │ ├── requests │ │ ├── campaigns_spec.rb │ │ ├── controls_spec.rb │ │ ├── organizations_spec.rb │ │ ├── profiles_spec.rb │ │ ├── sessions_request_spec.rb │ │ ├── sources_spec.rb │ │ └── users_spec.rb │ ├── routing │ │ ├── campaigns_routing_spec.rb │ │ ├── controls_routing_spec.rb │ │ ├── organizations_routing_spec.rb │ │ ├── profiles_routing_spec.rb │ │ ├── sources_routing_spec.rb │ │ └── users_routing_spec.rb │ └── spec_helper.rb ├── test │ ├── channels │ │ └── application_cable │ │ │ └── connection_test.rb │ ├── controllers │ │ └── .keep │ ├── fixtures │ │ ├── .keep │ │ └── files │ │ │ └── .keep │ ├── integration │ │ └── .keep │ ├── mailers │ │ └── .keep │ ├── models │ │ └── .keep │ └── test_helper.rb └── ui │ ├── .browserslistrc │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .prettierrc │ ├── babel.config.js │ ├── cypress.json │ ├── cypress │ ├── fixtures │ │ └── example.json │ ├── integration │ │ ├── examples │ │ │ ├── actions.spec.js │ │ │ ├── aliasing.spec.js │ │ │ ├── assertions.spec.js │ │ │ ├── connectors.spec.js │ │ │ ├── cookies.spec.js │ │ │ ├── cypress_api.spec.js │ │ │ ├── files.spec.js │ │ │ ├── local_storage.spec.js │ │ │ ├── location.spec.js │ │ │ ├── misc.spec.js │ │ │ ├── navigation.spec.js │ │ │ ├── network_requests.spec.js │ │ │ ├── querying.spec.js │ │ │ ├── spies_stubs_clocks.spec.js │ │ │ ├── traversal.spec.js │ │ │ ├── utilities.spec.js │ │ │ ├── viewport.spec.js │ │ │ ├── waiting.spec.js │ │ │ └── window.spec.js │ │ └── smoke_spec.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ └── index.js │ ├── init.sh │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── app.js │ ├── favicon.png │ ├── img │ │ └── logos │ │ │ ├── opencspm-logo-on-dark.svg │ │ │ ├── opencspm-logo-on-light.svg │ │ │ ├── opencspm-mark-on-light.png │ │ │ └── opencspm-mark-on-light.svg │ └── index.html │ ├── readme.md │ ├── src │ ├── App.vue │ ├── assets │ │ ├── css │ │ │ ├── custom.css │ │ │ └── tailwind.css │ │ └── logo_goes_here.png │ ├── components │ │ ├── Dashboard.vue │ │ ├── Sources.vue │ │ ├── admin │ │ │ ├── Admin.vue │ │ │ └── AdminCard.vue │ │ ├── campaign │ │ │ ├── Campaign.vue │ │ │ ├── CampaignActivity.vue │ │ │ ├── CampaignDropdown.vue │ │ │ ├── CampaignSettings.vue │ │ │ ├── Campaigns.vue │ │ │ └── NewCampaign.vue │ │ ├── control │ │ │ ├── ControlModal.vue │ │ │ ├── Controls.vue │ │ │ ├── ControlsList.vue │ │ │ ├── ControlsSearch.vue │ │ │ ├── ControlsSearchSelectImpact.vue │ │ │ ├── ControlsSearchSelectStatus.vue │ │ │ └── ControlsSearchSelectTags.vue │ │ ├── dashboard │ │ │ ├── StartCampaign.vue │ │ │ └── SuggestedCampaigns.vue │ │ ├── nav │ │ │ └── Nav.vue │ │ ├── org │ │ │ └── NewOrganization.vue │ │ ├── profile │ │ │ ├── Profile.vue │ │ │ ├── ProfileCard.vue │ │ │ ├── ProfileSearch.vue │ │ │ └── Profiles.vue │ │ ├── result │ │ │ ├── Finding.vue │ │ │ ├── Findings.vue │ │ │ ├── Modal.vue │ │ │ └── Results.vue │ │ ├── session │ │ │ └── NewSession.vue │ │ ├── settings │ │ │ └── DataSources.vue │ │ └── shared │ │ │ ├── BarChart.js │ │ │ ├── BarChartChartjs.vue │ │ │ ├── IconFilter.vue │ │ │ ├── NotFound.vue │ │ │ ├── OmniSearch.vue │ │ │ ├── StatusDot.vue │ │ │ ├── Tag.vue │ │ │ └── Tooltip.vue │ ├── main.js │ └── router │ │ └── index.js │ ├── tailwind.config.js │ └── yarn.lock └── site ├── data_collection.md ├── development.md ├── getting_started.md └── img ├── docker-dashboard.png ├── high-level-arch.excalidraw ├── high-level-arch.png ├── opencspm-logo.png ├── opencspm-quickstart-keyframe.png └── opencspm-s3-search.gif /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **OpenCSPM version** 14 | What version of the OpenCSPM Docker image are you using? (e.g. 0.1.22) 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Docker information (please complete the following information):** 30 | - Output of full `docker version` (not `docker -v`) 31 | - Output of full `docker-compose version` (not `docker-compose -v`) 32 | 33 | **Cloud provider (if applicable):** 34 | - e.g. AWS/GCP/Azure 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: docker-build 2 | 3 | on: 4 | push: 5 | branches: build 6 | 7 | jobs: 8 | docker-build: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 1 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v1 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v1 19 | - name: Configure AWS credentials 20 | uses: aws-actions/configure-aws-credentials@v1 21 | with: 22 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 23 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 24 | aws-region: us-east-1 25 | - name: Login to Amazon ECR 26 | id: login-ecr 27 | uses: aws-actions/amazon-ecr-login@v1 28 | - name: Login to GCR 29 | uses: docker/login-action@v1 30 | with: 31 | registry: gcr.io 32 | username: _json_key 33 | password: ${{ secrets.GCR_JSON_KEY }} 34 | - name: Set version tag 35 | run: | 36 | echo "VERSION_TAG=$(grep 'image: gcr.io/opencspm/engine' docker/docker-compose.yml | awk -F\: '{print $3}')" >> $GITHUB_ENV 37 | - name: Build and push 38 | uses: docker/build-push-action@v2 39 | with: 40 | context: ./docker 41 | file: ./docker/.dockerdev/Dockerfile 42 | push: true 43 | tags: | 44 | 971395506693.dkr.ecr.us-east-1.amazonaws.com/opencspm/engine:${{ env.VERSION_TAG }} 45 | 971395506693.dkr.ecr.us-east-1.amazonaws.com/opencspm/engine:latest 46 | gcr.io/opencspm/engine:${{ env.VERSION_TAG }} 47 | gcr.io/opencspm/engine:latest 48 | -------------------------------------------------------------------------------- /.github/workflows/release-notification.yml: -------------------------------------------------------------------------------- 1 | name: release-notification 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release-notification: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - name: Send Slack Message 12 | run: | 13 | curl -X POST -H 'Content-type: application/json' --data '{"text":"New OpenCSPM release: ${{ github.event.release.tag_name }} - https://github.com/opencspm/opencspm/releases/tag/${{ github.event.release.tag_name }}"}' ${{ secrets.SLACK_WEBHOOK_URL }} 14 | -------------------------------------------------------------------------------- /.github/workflows/smoke-test.yml: -------------------------------------------------------------------------------- 1 | name: smoke-test 2 | 3 | on: 4 | push: 5 | branches: main 6 | 7 | jobs: 8 | smoke-test: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 1 15 | - name: Install Control Packs 16 | run: | 17 | cd ..; mkdir opencspm-controls; cd opencspm-controls; git clone https://github.com/OpenCSPM/opencspm-darkbit-community-controls.git 18 | - name: Docker Compose Up 19 | run: | 20 | cd docker; docker-compose up -d 21 | - name: Smoke Test (10 min) 22 | run: | 23 | timeout 600 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:5000/statusz)" != "200" ]]; do sleep 5; done' || false 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | docker/tmp 4 | docker/log 5 | .init 6 | .viminfo 7 | .bash_history 8 | *.swp 9 | .env 10 | .idea 11 | 12 | # Ruby / RMV 13 | .ruby-version 14 | .ruby-gemset 15 | .bundle 16 | .gem 17 | .irb_history 18 | 19 | # Docker 20 | .psqlrc 21 | .bashrc 22 | 23 | # UI 24 | docker/ui/package-lock.json 25 | docker/ui/dist/* 26 | docker/ui/cypress/videos/* 27 | docker/ui/cypress/screenshots/* 28 | 29 | # Rails 30 | .local 31 | *.rbc 32 | capybara-*.html 33 | /public/system 34 | public/* 35 | /coverage/ 36 | /spec/tmp 37 | *.orig 38 | 39 | # Ignore test data 40 | assets/test/* 41 | assets/custom/* 42 | 43 | # Ignore all logfiles and tempfiles. 44 | /log/* 45 | /tmp/* 46 | !/log/.keep 47 | !/tmp/.keep 48 | 49 | # TODO Comment out this rule if you are OK with secrets being uploaded to the repo 50 | config/initializers/secret_token.rb 51 | config/master.key 52 | master.key 53 | 54 | # dotenv 55 | #.env 56 | 57 | # Environment normalization: 58 | /.bundle 59 | /vendor/bundle 60 | 61 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 62 | .rvmrc 63 | 64 | # Ignore Byebug command history file. 65 | .byebug_history 66 | 67 | # Ignore node_modules 68 | node_modules/ 69 | 70 | # Ignore precompiled javascript packs 71 | /public/packs 72 | /public/packs-test 73 | /public/assets 74 | 75 | # Ignore yarn files 76 | .yarnrc 77 | yarn-error.log 78 | yarn-debug.log* 79 | .yarn-integrity 80 | 81 | # Ignore uploaded files in development 82 | /storage/* 83 | !/storage/.keep 84 | 85 | # Ignore pry 86 | docker/.local/* 87 | 88 | # tmp load files 89 | docker/load_dir/*.json 90 | docker/load_dir/tmp-* 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Darkbit, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/demo/aws/manifest.txt: -------------------------------------------------------------------------------- 1 | aws_recon_sample.json 2 | -------------------------------------------------------------------------------- /assets/demo/gcp-iam/manifest.txt: -------------------------------------------------------------------------------- 1 | full-iam-permissions.json 2 | -------------------------------------------------------------------------------- /assets/demo/gcp/manifest.txt: -------------------------------------------------------------------------------- 1 | 1590603998-iam-policy.json 2 | 1590603998-resource.json 3 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | README.md 3 | .ruby-version 4 | .bundle/ 5 | vendor/ 6 | build.sh 7 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official lightweight Ruby image. 2 | # https://hub.docker.com/_/ruby 3 | FROM ruby:2.7-slim 4 | 5 | # Install production dependencies. 6 | WORKDIR /usr/src/app 7 | COPY Gemfile Gemfile.lock ./ 8 | ENV BUNDLE_FROZEN=true 9 | RUN apt-get update && \ 10 | apt-get install build-essential -y && \ 11 | gem install bundler:1.17.3 google-cloud-storage google-cloud-asset && \ 12 | bundle install --without test && \ 13 | apt-get remove build-essential -y 14 | 15 | # Copy local code to the container image. 16 | COPY . ./ 17 | 18 | # Run the web service on container startup. 19 | CMD ["ruby", "./app.rb"] 20 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "sinatra", "~>2.0" 4 | 5 | group :test do 6 | gem "rack-test" 7 | gem "rest-client" 8 | gem "rspec" 9 | gem "rspec_junit_formatter" 10 | gem "rubysl-securerandom" 11 | end 12 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | diff-lcs (1.4.4) 5 | domain_name (0.5.20190701) 6 | unf (>= 0.0.5, < 1.0.0) 7 | http-accept (1.7.0) 8 | http-cookie (1.0.3) 9 | domain_name (~> 0.5) 10 | mime-types (3.3.1) 11 | mime-types-data (~> 3.2015) 12 | mime-types-data (3.2020.0512) 13 | mustermann (1.1.1) 14 | ruby2_keywords (~> 0.0.1) 15 | netrc (0.11.0) 16 | rack (2.2.3) 17 | rack-protection (2.1.0) 18 | rack 19 | rack-test (1.1.0) 20 | rack (>= 1.0, < 3) 21 | rest-client (2.1.0) 22 | http-accept (>= 1.7.0, < 2.0) 23 | http-cookie (>= 1.0.2, < 2.0) 24 | mime-types (>= 1.16, < 4.0) 25 | netrc (~> 0.8) 26 | rspec (3.9.0) 27 | rspec-core (~> 3.9.0) 28 | rspec-expectations (~> 3.9.0) 29 | rspec-mocks (~> 3.9.0) 30 | rspec-core (3.9.3) 31 | rspec-support (~> 3.9.3) 32 | rspec-expectations (3.9.2) 33 | diff-lcs (>= 1.2.0, < 2.0) 34 | rspec-support (~> 3.9.0) 35 | rspec-mocks (3.9.1) 36 | diff-lcs (>= 1.2.0, < 2.0) 37 | rspec-support (~> 3.9.0) 38 | rspec-support (3.9.3) 39 | rspec_junit_formatter (0.4.1) 40 | rspec-core (>= 2, < 4, != 2.12.0) 41 | ruby2_keywords (0.0.2) 42 | rubysl-securerandom (2.0.0) 43 | sinatra (2.1.0) 44 | mustermann (~> 1.0) 45 | rack (~> 2.2) 46 | rack-protection (= 2.1.0) 47 | tilt (~> 2.0) 48 | tilt (2.0.10) 49 | unf (0.1.4) 50 | unf_ext 51 | unf_ext (0.0.7.7) 52 | 53 | PLATFORMS 54 | ruby 55 | 56 | DEPENDENCIES 57 | rack-test 58 | rest-client 59 | rspec 60 | rspec_junit_formatter 61 | rubysl-securerandom 62 | sinatra (~> 2.0) 63 | 64 | BUNDLED WITH 65 | 1.17.3 66 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/app.rb: -------------------------------------------------------------------------------- 1 | require "sinatra" 2 | require_relative "asset" 3 | 4 | set :bind, "0.0.0.0" 5 | set :port, "8080" 6 | 7 | get "/" do 8 | "" 9 | end 10 | 11 | get "/healthz" do 12 | "ok" 13 | end 14 | 15 | get "/exportcai" do 16 | logger.info "CAI Export Called via Cloud Run" 17 | export_gcp_cai 18 | "done" 19 | end 20 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/asset.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require "google/cloud/asset" 3 | require "google/cloud/storage" 4 | 5 | def export_gcp_cai 6 | parent_path = ENV['CAI_PARENT_PATH'] 7 | bucket_name = ENV['GCS_BUCKET_NAME'] 8 | bucket_folder = ENV['GCS_BUCKET_FOLDER'] || "cai" 9 | bucket_path = "gs://#{bucket_name}/#{bucket_folder}" 10 | 11 | asset_service = Google::Cloud::Asset.asset_service 12 | 13 | manifest_data = [] 14 | current_utc = Time.now.to_i 15 | %w{ RESOURCE IAM_POLICY ORG_POLICY ACCESS_POLICY }.each do |ctype| 16 | export_file_name = "#{current_utc}-#{ctype.downcase}.json" 17 | output_config = { gcs_destination: { uri: "#{bucket_path}/#{export_file_name}" } } 18 | 19 | operation = asset_service.export_assets( parent: parent_path, content_type: ctype, output_config: output_config) do |op| 20 | # Handle the error. 21 | logger.error "ERROR: #{op.results.message}" if op.error? 22 | raise op.results.message if op.error? 23 | end 24 | 25 | operation.wait_until_done! 26 | response = operation.response 27 | logger.info "Exported assets to: #{response.output_config.gcs_destination.uri}" 28 | manifest_data << export_file_name 29 | end 30 | write_manifest(bucket_name, bucket_folder, manifest_data) 31 | end 32 | 33 | def write_manifest(bucket_name, bucket_folder, manifest_data) 34 | storage = Google::Cloud::Storage.new 35 | bucket = storage.bucket(bucket_name) 36 | 37 | IO.write("/tmp/manifest", manifest_data.join("\n")) 38 | uploaded_manifest = bucket.create_file("/tmp/manifest", "#{bucket_folder}/manifest.txt") 39 | logger.info "Uploaded #{uploaded_manifest.name}" 40 | end 41 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IMAGE="opencspm/gcp-cai-exporter" 4 | TAG="$(cat version)" 5 | 6 | gcloud builds submit --tag "gcr.io/${IMAGE}:${TAG}" 7 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/buildlocally.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IMAGE="opencspm/gcp-cai-exporter" 4 | TAG="$(cat version)" 5 | 6 | docker build --tag "gcr.io/${IMAGE}:${TAG}" . 7 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/runlocally.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/opencspm-collection.json 3 | IMAGE="opencspm/gcp-cai-exporter" 4 | TAG="$(cat version)" 5 | 6 | CAI_PARENT_PATH="organizations/1071234196403" 7 | GCS_BUCKET_NAME="darkbit-collection-us-cspm" 8 | GCS_BUCKET_FOLDER="cai" 9 | PORT=9090 10 | 11 | docker run \ 12 | -p ${PORT}:8080 \ 13 | -e CAI_PARENT_PATH="${CAI_PARENT_PATH}" \ 14 | -e GCS_BUCKET_NAME="${GCS_BUCKET_NAME}" \ 15 | -e GCS_BUCKET_FOLDER="${GCS_BUCKET_FOLDER}" \ 16 | -e K_SERVICE=dev \ 17 | -e K_CONFIGURATION=dev \ 18 | -e K_REVISION=dev-00001 \ 19 | -e GOOGLE_APPLICATION_CREDENTIALS=/tmp/keys/credentials.json \ 20 | -v $GOOGLE_APPLICATION_CREDENTIALS:/tmp/keys/credentials.json:ro \ 21 | "gcr.io/${IMAGE}:${TAG}" 22 | -------------------------------------------------------------------------------- /collection/gcp-cai-exporter/version: -------------------------------------------------------------------------------- 1 | v0.1.5 2 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | README.md 3 | .ruby-version 4 | .bundle/ 5 | vendor/ 6 | build.sh 7 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official lightweight Ruby image. 2 | # https://hub.docker.com/_/ruby 3 | FROM ruby:2.7-slim 4 | 5 | # Install production dependencies. 6 | WORKDIR /usr/src/app 7 | COPY Gemfile Gemfile.lock ./ 8 | ENV BUNDLE_FROZEN=true 9 | RUN apt-get update && \ 10 | apt-get install build-essential -y && \ 11 | gem install bundler:1.17.3 google-cloud-storage && \ 12 | bundle install --without test && \ 13 | apt-get remove build-essential -y 14 | 15 | # Copy local code to the container image. 16 | COPY . ./ 17 | 18 | # Run the web service on container startup. 19 | CMD ["ruby", "./app.rb"] 20 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "sinatra", "~>2.0" 4 | 5 | group :test do 6 | gem "rack-test" 7 | gem "rest-client" 8 | gem "rspec" 9 | gem "rspec_junit_formatter" 10 | gem "rubysl-securerandom" 11 | end 12 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | diff-lcs (1.4.4) 5 | domain_name (0.5.20190701) 6 | unf (>= 0.0.5, < 1.0.0) 7 | http-accept (1.7.0) 8 | http-cookie (1.0.3) 9 | domain_name (~> 0.5) 10 | mime-types (3.3.1) 11 | mime-types-data (~> 3.2015) 12 | mime-types-data (3.2020.0512) 13 | mustermann (1.1.1) 14 | ruby2_keywords (~> 0.0.1) 15 | netrc (0.11.0) 16 | rack (2.2.3) 17 | rack-protection (2.1.0) 18 | rack 19 | rack-test (1.1.0) 20 | rack (>= 1.0, < 3) 21 | rest-client (2.1.0) 22 | http-accept (>= 1.7.0, < 2.0) 23 | http-cookie (>= 1.0.2, < 2.0) 24 | mime-types (>= 1.16, < 4.0) 25 | netrc (~> 0.8) 26 | rspec (3.9.0) 27 | rspec-core (~> 3.9.0) 28 | rspec-expectations (~> 3.9.0) 29 | rspec-mocks (~> 3.9.0) 30 | rspec-core (3.9.3) 31 | rspec-support (~> 3.9.3) 32 | rspec-expectations (3.9.2) 33 | diff-lcs (>= 1.2.0, < 2.0) 34 | rspec-support (~> 3.9.0) 35 | rspec-mocks (3.9.1) 36 | diff-lcs (>= 1.2.0, < 2.0) 37 | rspec-support (~> 3.9.0) 38 | rspec-support (3.9.3) 39 | rspec_junit_formatter (0.4.1) 40 | rspec-core (>= 2, < 4, != 2.12.0) 41 | ruby2_keywords (0.0.2) 42 | rubysl-securerandom (2.0.0) 43 | sinatra (2.1.0) 44 | mustermann (~> 1.0) 45 | rack (~> 2.2) 46 | rack-protection (= 2.1.0) 47 | tilt (~> 2.0) 48 | tilt (2.0.10) 49 | unf (0.1.4) 50 | unf_ext 51 | unf_ext (0.0.7.7) 52 | 53 | PLATFORMS 54 | ruby 55 | 56 | DEPENDENCIES 57 | rack-test 58 | rest-client 59 | rspec 60 | rspec_junit_formatter 61 | rubysl-securerandom 62 | sinatra (~> 2.0) 63 | 64 | BUNDLED WITH 65 | 1.17.3 66 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/app.rb: -------------------------------------------------------------------------------- 1 | require "sinatra" 2 | require_relative "iam" 3 | 4 | set :bind, "0.0.0.0" 5 | set :port, "8080" 6 | 7 | get "/" do 8 | "" 9 | end 10 | 11 | get "/healthz" do 12 | "ok" 13 | end 14 | 15 | get "/exportcai" do 16 | logger.info "IAM Export Called via Cloud Run" 17 | export_iam_cai 18 | "done" 19 | end 20 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IMAGE="opencspm/gcp-iam-exporter" 4 | TAG="$(cat version)" 5 | 6 | gcloud builds submit --tag "gcr.io/${IMAGE}:${TAG}" 7 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/buildlocally.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IMAGE="opencspm/gcp-iam-exporter" 4 | TAG="$(cat version)" 5 | 6 | docker build --tag "gcr.io/${IMAGE}:${TAG}" . 7 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/iam.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'google/cloud/storage' 3 | require 'open-uri' 4 | 5 | def export_iam_cai 6 | bucket_name = ENV['GCS_BUCKET_NAME'] 7 | bucket_folder = ENV['GCS_BUCKET_FOLDER'] || "iam" 8 | bucket_path = "gs://#{bucket_name}/#{bucket_folder}" 9 | iam_cai_uri = ENV['IAM_CAI_URI'] || "https://github.com/darkbitio/gcp-iam-role-permissions/raw/master/gcp_roles_cai.json" 10 | 11 | current_utc = Time.now.to_i 12 | export_file_name = "#{current_utc}-full-iam-export.json" 13 | 14 | iam_cai_data = URI.parse(iam_cai_uri).read 15 | 16 | # Write the file locally 17 | tmp_file_path = "/tmp/iam-roles.json" 18 | IO.write(tmp_file_path, iam_cai_data) 19 | 20 | # Write the iam roles file to the bucket 21 | write_iam_file(bucket_name, bucket_folder, tmp_file_path, export_file_name) 22 | 23 | # Write the manifest to the bucket 24 | manifest_data = [] 25 | manifest_data << export_file_name 26 | write_manifest(bucket_name, bucket_folder, manifest_data) 27 | end 28 | 29 | def write_iam_file(bucket_name, bucket_folder, tmp_file, export_file_name) 30 | storage = Google::Cloud::Storage.new 31 | bucket = storage.bucket(bucket_name) 32 | 33 | uploaded_manifest = bucket.create_file(tmp_file, "#{bucket_folder}/#{export_file_name}") 34 | logger.info "Uploaded #{uploaded_manifest.name}" 35 | end 36 | 37 | def write_manifest(bucket_name, bucket_folder, manifest_data) 38 | storage = Google::Cloud::Storage.new 39 | bucket = storage.bucket(bucket_name) 40 | 41 | IO.write("/tmp/manifest", manifest_data.join("\n")) 42 | uploaded_manifest = bucket.create_file("/tmp/manifest", "#{bucket_folder}/manifest.txt") 43 | logger.info "Uploaded #{uploaded_manifest.name}" 44 | end 45 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/runlocally.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/opencspm-collection.json 3 | IMAGE="opencspm/gcp-iam-exporter" 4 | TAG="$(cat version)" 5 | 6 | CAI_PARENT_PATH="organizations/1071234196403" 7 | GCS_BUCKET_NAME="darkbit-collection-us-cspm" 8 | GCS_BUCKET_FOLDER="iam" 9 | PORT=9090 10 | 11 | docker run \ 12 | -p ${PORT}:8080 \ 13 | -e CAI_PARENT_PATH="${CAI_PARENT_PATH}" \ 14 | -e GCS_BUCKET_NAME="${GCS_BUCKET_NAME}" \ 15 | -e GCS_BUCKET_FOLDER="${GCS_BUCKET_FOLDER}" \ 16 | -e K_SERVICE=dev \ 17 | -e K_CONFIGURATION=dev \ 18 | -e K_REVISION=dev-00001 \ 19 | -e GOOGLE_APPLICATION_CREDENTIALS=/tmp/keys/credentials.json \ 20 | -v $GOOGLE_APPLICATION_CREDENTIALS:/tmp/keys/credentials.json:ro \ 21 | "gcr.io/${IMAGE}:${TAG}" 22 | -------------------------------------------------------------------------------- /collection/gcp-iam-exporter/version: -------------------------------------------------------------------------------- 1 | v0.1.2 2 | -------------------------------------------------------------------------------- /collection/k8s-cai-exporter/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | README.md 3 | build.sh 4 | runlocally.sh 5 | buildlocally.sh 6 | cronjob.yaml.sh 7 | -------------------------------------------------------------------------------- /collection/k8s-cai-exporter/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/google.com/cloudsdktool/cloud-sdk:331.0.0-alpine 2 | RUN apk update && \ 3 | apk add jq curl vim && \ 4 | curl -sLO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \ 5 | chmod +x ./kubectl && \ 6 | mv ./kubectl /usr/local/bin 7 | 8 | ENV HOME=/tmp 9 | WORKDIR /tmp 10 | 11 | ADD k8s-export.sh /tmp/k8s-export.sh 12 | RUN chmod +x /tmp/k8s-export.sh 13 | 14 | CMD ["/tmp/k8s-export.sh"] 15 | -------------------------------------------------------------------------------- /collection/k8s-cai-exporter/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IMAGE="opencspm/k8s-cai-exporter" 4 | TAG="$(cat version)" 5 | 6 | gcloud config set project opencspm 7 | gcloud builds submit --tag "gcr.io/${IMAGE}:${TAG}" 8 | 9 | IMAGE="darkbit-io/gke-exporter" 10 | 11 | gcloud config set project darkbit-io 12 | gcloud builds submit --tag "gcr.io/${IMAGE}:${TAG}" 13 | -------------------------------------------------------------------------------- /collection/k8s-cai-exporter/buildlocally.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IMAGE="opencspm/k8s-cai-exporter" 4 | TAG="$(cat version)" 5 | 6 | docker build --tag "gcr.io/${IMAGE}:${TAG}" . 7 | -------------------------------------------------------------------------------- /collection/k8s-cai-exporter/runlocally.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/opencspm-collection.json 3 | IMAGE="opencspm/k8s-cai-exporter" 4 | TAG="$(cat version)" 5 | 6 | GCS_BUCKET_NAME="darkbit-collection-us-cspm" 7 | GCS_BUCKET_FOLDER="k8s" 8 | PORT=9090 9 | 10 | docker run --rm --net=host \ 11 | -p ${PORT}:8080 \ 12 | -e GCS_BUCKET_NAME="${GCS_BUCKET_NAME}" \ 13 | -e GCS_BUCKET_FOLDER="${GCS_BUCKET_FOLDER}" \ 14 | -e K_SERVICE=dev \ 15 | -e K_CONFIGURATION=dev \ 16 | -e K_REVISION=dev-00001 \ 17 | -e GOOGLE_APPLICATION_CREDENTIALS=/tmp/keys/credentials.json \ 18 | -v $GOOGLE_APPLICATION_CREDENTIALS:/tmp/keys/credentials.json:ro \ 19 | -v /Users/bg/.kube/config:/root/.kube/config:ro \ 20 | "gcr.io/${IMAGE}:${TAG}" 21 | -------------------------------------------------------------------------------- /collection/k8s-cai-exporter/version: -------------------------------------------------------------------------------- 1 | v0.1.10 2 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | db: 3 | host: redis 4 | port: 6379 5 | buckets: 6 | # - gs://darkbit-collection-us-cspm 7 | # - s3://my-other-bucket-here 8 | local_dirs: 9 | #- /app/data/demo 10 | - /app/data/custom 11 | -------------------------------------------------------------------------------- /docker/.dockerdev/.bashrc: -------------------------------------------------------------------------------- 1 | alias be="bundle exec" 2 | alias ll="ls -l --color" 3 | alias la="ls -al --color" 4 | -------------------------------------------------------------------------------- /docker/.dockerdev/.psqlrc: -------------------------------------------------------------------------------- 1 | -- Don't display the "helpful" message on startup. 2 | \set QUIET 1 3 | 4 | -- Allow specifying the path to history file via `PSQL_HISTFILE` env variable 5 | -- (and fallback to the default $HOME/.psql_history otherwise) 6 | \set HISTFILE `[[ -z $PSQL_HISTFILE ]] && echo $HOME/.psql_history || echo $PSQL_HISTFILE` 7 | 8 | -- Show how long each query takes to execute 9 | \timing 10 | 11 | -- Use best available output format 12 | \x auto 13 | 14 | -- Verbose error reports 15 | \set VERBOSITY verbose 16 | 17 | -- If a command is run more than once in a row, 18 | -- only store it once in the history 19 | \set HISTCONTROL ignoredups 20 | \set COMP_KEYWORD_CASE upper 21 | 22 | -- By default, NULL displays as an empty space. Is it actually an empty 23 | -- string, or is it null? This makes that distinction visible 24 | \pset null '[NULL]' 25 | 26 | \unset QUIET 27 | -------------------------------------------------------------------------------- /docker/.dockerdev/Aptfile: -------------------------------------------------------------------------------- 1 | vim 2 | -------------------------------------------------------------------------------- /docker/.dockerignore: -------------------------------------------------------------------------------- 1 | # dot files 2 | .DS_Store 3 | .bash_history 4 | .bashrc 5 | .bundle 6 | .cache 7 | .dockerdev/Dockerfile* 8 | .dockerignore 9 | .env 10 | .init 11 | .irb_history 12 | .irbrc 13 | .local 14 | .rubocop.yml 15 | .ruby-gemset 16 | .ruby-version 17 | .solargraph.yml 18 | .viminfo 19 | .yarn 20 | .yarnrc 21 | 22 | # build artifacts 23 | Gemfile.lock 24 | build.sh 25 | docker-* 26 | 27 | # core 28 | readme.md 29 | controls 30 | data 31 | load_config 32 | load_dir/*.json 33 | log 34 | public/* 35 | tmp 36 | 37 | # frontend 38 | ui/.init 39 | ui/yarn.lock 40 | ui/node_modules 41 | ui/cypress 42 | ui/cypress.json 43 | -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=opencspm 2 | -------------------------------------------------------------------------------- /docker/.irbrc: -------------------------------------------------------------------------------- 1 | IRB.conf[:SAVE_HISTORY] = 1000 2 | -------------------------------------------------------------------------------- /docker/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /docker/.rubocop.yml: -------------------------------------------------------------------------------- 1 | # The behavior of RuboCop can be controlled via the .rubocop.yml 2 | # configuration file. It makes it possible to enable/disable 3 | # certain cops (checks) and to alter their behavior if they accept 4 | # any parameters. The file can be placed either in your home 5 | # directory or in some project directory. 6 | # 7 | # RuboCop will start looking for the configuration file in the directory 8 | # where the inspected file is and continue its way up to the root directory. 9 | # 10 | # See https://docs.rubocop.org/rubocop/configuration 11 | Layout/LineLength: 12 | Max: 120 13 | Style/FrozenStringLiteralComment: 14 | EnforcedStyle: always_true 15 | Safe: true 16 | SafeAutoCorrect: true 17 | Style/ClassAndModuleChildren: 18 | Enabled: false 19 | Metrics/BlockLength: 20 | Enabled: false 21 | Metrics/MethodLength: 22 | Enabled: false 23 | Metrics/PerceivedComplexity: 24 | Enabled: false 25 | Metrics/CyclomaticComplexity: 26 | Enabled: false 27 | Metrics/AbcSize: 28 | Enabled: false 29 | -------------------------------------------------------------------------------- /docker/.solargraph.yml: -------------------------------------------------------------------------------- 1 | --- 2 | include: 3 | - "**/*.rb" 4 | exclude: 5 | - spec/**/* 6 | - test/**/* 7 | - vendor/**/* 8 | - ".bundle/**/*" 9 | require: [] 10 | domains: [] 11 | reporters: 12 | - rubocop 13 | - require_not_found 14 | require_paths: [] 15 | plugins: [] 16 | max_files: 5000 17 | -------------------------------------------------------------------------------- /docker/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "OpenCSPM Rails debugger", 6 | "type": "Ruby", 7 | "cwd": "${workspaceRoot}", 8 | "request": "attach", 9 | "remoteHost": "127.0.0.1", 10 | "showDebuggerOutput": true, 11 | "remotePort": "4444", 12 | "remoteWorkspaceRoot": "/app" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /docker/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '2.7.3' 5 | 6 | gem 'rails', '~> 6.1.3', '>= 6.1.3' 7 | gem 'pg', '>= 0.18', '< 2.0' 8 | gem 'puma', '~> 4.1' 9 | gem 'bootsnap', '>= 1.4.2', require: false 10 | gem 'rack-cors', '~> 1.1' 11 | 12 | # app gems 13 | gem 'listen', '~> 3.2' 14 | gem 'redis', '~> 4.2' 15 | gem 'redisgraph', '~> 2.0' 16 | gem 'sidekiq', '~> 6.1' 17 | gem 'sidekiq-cron', '~> 1.2' 18 | gem 'parallel', '~> 1.20' 19 | gem 'fast_jsonapi', '~> 1.5' 20 | gem 'recursive-open-struct', '~> 1.1' 21 | gem 'pundit', '~> 2.1' 22 | gem 'bcrypt', '~> 3.1' 23 | gem 'omniauth', '~> 2.0', '>= 2.0.0' 24 | gem 'omniauth-google-oauth2', '~> 0.8.0' 25 | gem 'omniauth-github', '~> 2.0', '>= 2.0.0' 26 | gem 'google-cloud-storage', '~> 1.29' 27 | gem 'rolify', '~> 5.3' 28 | gem 'lograge', '~> 0.11.2' 29 | gem 'rspec', '~> 3.9' 30 | gem 'addressable', '~> 2.7' 31 | gem 'json_schemer', '~> 0.2.15' 32 | gem 'fast_jsonparser', '~> 0.5' 33 | gem 'pry', '~> 0.13.1' 34 | gem 'kramdown', '~> 2.3.1' # update for CVE-2021-28834 35 | 36 | group :development do 37 | gem 'spring' 38 | gem 'spring-watcher-listen', '~> 2.0.0' 39 | 40 | # app gems 41 | gem 'awesome_print', '~> 1.8' 42 | gem 'solargraph', '~> 0.40.0' 43 | gem 'better_errors', '~> 2.7' 44 | gem 'rubocop', '~> 0.90.0' 45 | gem 'binding_of_caller', '~> 0.8.0' 46 | end 47 | 48 | group :development, :test do 49 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 50 | 51 | # app gems 52 | gem 'debase', '~> 0.2.4' 53 | gem 'factory_bot_rails', '~> 6.1', '>= 6.1.0' 54 | gem 'rspec-rails', '~> 4.0', '>= 4.0.1' 55 | gem 'ruby-debug-ide', '~> 0.7.2' 56 | end 57 | -------------------------------------------------------------------------------- /docker/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /docker/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /docker/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /docker/app/controllers/api/campaign_results_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::CampaignResultsController < ApplicationController 2 | before_action :set_campaign, only: %i[index] 3 | 4 | # GET /campaign/1/results 5 | def index 6 | render json: ResultsSerializer.new(@campaign.results) 7 | end 8 | 9 | private 10 | 11 | # Use callbacks to share common setup or constraints between actions. 12 | def set_campaign 13 | @campaign = Campaign.find_by_id!(params[:campaign_id]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docker/app/controllers/api/campaigns_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::CampaignsController < ApplicationController 2 | before_action :set_campaign, only: %i[show update destroy] 3 | 4 | # GET /campaigns 5 | def index 6 | @campaigns = Campaign.all 7 | 8 | render json: CampaignsSerializer.new(@campaigns) 9 | end 10 | 11 | # GET /campaigns/1 12 | def show 13 | render json: CampaignSerializer.new(@campaign) 14 | end 15 | 16 | # POST /campaigns 17 | def create 18 | user = current_user 19 | 20 | @campaign = user.campaigns.new(campaign_params) 21 | 22 | # default params 23 | @campaign.name = 'New Campaign' 24 | @campaign.organization_id = user.organization.id 25 | 26 | render json: CampaignSerializer.new(@campaign), status: :created if @campaign.save! 27 | end 28 | 29 | # PATCH/PUT /campaigns/1 30 | def update 31 | render json: CampaignSerializer.new(@campaign) if @campaign.update!(campaign_params) 32 | end 33 | 34 | # DELETE /campaigns/1 35 | def destroy 36 | @campaign.destroy 37 | end 38 | 39 | private 40 | 41 | # Use callbacks to share common setup or constraints between actions. 42 | def set_campaign 43 | @campaign = Campaign.find_by_id!(params[:id]) 44 | end 45 | 46 | # Only allow a trusted parameter "allow list" through. 47 | def campaign_params 48 | params.require(:campaign).permit(:name, :notes, filters: {}) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /docker/app/controllers/api/controls_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::ControlsController < ApplicationController 2 | before_action :set_control, only: %i[show destroy] 3 | before_action :set_campaign, only: %i[destroy] 4 | 5 | # GET /controls 6 | def index 7 | @controls = Control.with_mapped_tags 8 | 9 | render json: ControlsSerializer.new(@controls) 10 | end 11 | 12 | # GET /controls/1 13 | def show 14 | # render json: @control 15 | render json: ControlSerializer.new(@control) 16 | end 17 | 18 | # DELETE /campaigns/1/controls/2 19 | def destroy 20 | @campaign.find(@control.id).destroy if @control 21 | render json: CampaignSerializer.new(@campaign) 22 | end 23 | 24 | private 25 | 26 | # Use callbacks to share common setup or constraints between actions. 27 | def set_control 28 | @control = Control.find_by_id!(params[:id]) 29 | end 30 | 31 | def set_campaign 32 | @campaign = Campaign.find_by_id!(params[:campaign_id]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /docker/app/controllers/api/jobs_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::JobsController < ApplicationController 2 | # GET /jobs 3 | def index 4 | @jobs = Job.order(id: :desc).limit(10) 5 | 6 | render json: @jobs 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /docker/app/controllers/api/organizations_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::OrganizationsController < ApplicationController 2 | before_action :set_organization, only: %i[show update destroy] 3 | 4 | # GET /organizations 5 | def index 6 | @organizations = Organization.all 7 | 8 | render json: @organizations 9 | end 10 | 11 | # GET /organizations/1 12 | def show 13 | render json: @organization 14 | end 15 | 16 | # POST /organizations 17 | def create 18 | @organization = Organization.new(organization_params) 19 | 20 | if @organization.save 21 | render json: @organization, status: :created, location: @organization 22 | else 23 | render json: @organization.errors, status: :unprocessable_entity 24 | end 25 | end 26 | 27 | # PATCH/PUT /organizations/1 28 | def update 29 | if @organization.update(organization_params) 30 | render json: @organization 31 | else 32 | render json: @organization.errors, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | # DELETE /organizations/1 37 | def destroy 38 | @organization.destroy 39 | end 40 | 41 | private 42 | 43 | # Use callbacks to share common setup or constraints between actions. 44 | def set_organization 45 | @organization = Organization.find(params[:id]) 46 | end 47 | 48 | # Only allow a trusted parameter "allow list" through. 49 | def organization_params 50 | params.require(:organization).permit(:name) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /docker/app/controllers/api/profiles_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::ProfilesController < ApplicationController 2 | before_action :set_profile, only: %i[show] 3 | 4 | # GET /profiles 5 | def index 6 | # @profiles = Profile.all 7 | @profiles = Profile.all 8 | 9 | # render json: @profiles 10 | render json: ProfilesSerializer.new(@profiles) 11 | end 12 | 13 | # GET /profiles/1 14 | def show 15 | render json: ProfileSerializer.new(@profile) 16 | end 17 | 18 | private 19 | 20 | # Use callbacks to share common setup or constraints between actions. 21 | def set_profile 22 | @profile = Profile.find_by_id!(params[:id]) 23 | end 24 | 25 | # Only allow a trusted parameter "allow list" through. 26 | def profile_params 27 | params.require(:profile).permit(:name, :author, :provider, :category, :status) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /docker/app/controllers/api/results_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::ResultsController < ApplicationController 2 | # GET /results 3 | def index 4 | @issue_count = Profile.active.sum(:issue_count) 5 | 6 | render json: { issue_count: @issue_count } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /docker/app/controllers/api/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Validates whether or not there is there a valid session 3 | # 4 | class Api::SessionsController < ApplicationController 5 | skip_before_action :authenticate_user! 6 | skip_forgery_protection only: :destroy 7 | 8 | def create 9 | if request.env['omniauth.auth'] 10 | # oauth login 11 | @user = User.find_for_oauth(request.env['omniauth.auth']) 12 | session[:user_id] = @user.id 13 | redirect_to Rails.application.config.ui_host 14 | else 15 | # password login 16 | @user = User.find_by(username: session_params[:username]).try(:authenticate, session_params[:password]) 17 | 18 | if @user 19 | session[:user_id] = @user.id 20 | render json: {}, status: :ok 21 | else 22 | session[:user_id] = nil 23 | render json: {}, status: :unauthorized 24 | end 25 | end 26 | end 27 | 28 | # Nav helper 29 | # 30 | # GET /sessions 31 | # 32 | def index 33 | if @user = current_user 34 | render json: @user 35 | else 36 | render json: {}, status: :unauthorized 37 | end 38 | end 39 | 40 | # Nav helper 41 | # 42 | # GET /sessions/* 43 | # 44 | def show 45 | if user = current_user 46 | 47 | nav_items = user.get_nav_paths 48 | 49 | user_data = { 50 | user_data: { name: user.name, username: user.username, email_hash: Digest::MD5.hexdigest(user.username) }, 51 | nav_items: nav_items 52 | } 53 | 54 | render json: user_data 55 | else 56 | render json: {}, status: :unauthorized 57 | end 58 | end 59 | 60 | # DELETE /sessions 61 | def destroy 62 | session[:user_id] = nil 63 | render json: {}, status: :ok 64 | end 65 | 66 | private 67 | 68 | def session_params 69 | params.require(:session).permit(:username, :password) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /docker/app/controllers/api/sources_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Api::SourcesController < ApplicationController 4 | before_action :set_source, only: %i[show update destroy] 5 | 6 | # GET /sources 7 | def index 8 | # re-read config.yml sources 9 | Source.new.refresh 10 | 11 | @sources = Source.all.order(id: :asc) 12 | 13 | render json: @sources 14 | end 15 | 16 | # GET /sources/1 17 | def show 18 | render json: @source 19 | end 20 | 21 | # POST /sources 22 | def create 23 | @source = Source.new(source_params) 24 | 25 | if @source.save 26 | render json: @source, status: :created, location: @source 27 | else 28 | render json: @source.errors, status: :unprocessable_entity 29 | end 30 | end 31 | 32 | # PATCH/PUT /sources/1 33 | def update 34 | if @source.update(source_params) 35 | @source.schedule_worker 36 | render json: @source 37 | else 38 | render json: @source.errors, status: :unprocessable_entity 39 | end 40 | end 41 | 42 | # DELETE /sources/1 43 | def destroy 44 | @source.destroy 45 | end 46 | 47 | private 48 | 49 | # Use callbacks to share common setup or constraints between actions. 50 | def set_source 51 | @source = Source.find(params[:id]) 52 | end 53 | 54 | # Only allow a trusted parameter "allow list" through. 55 | def source_params 56 | params.require(:source).permit(:name, :status) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /docker/app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::UsersController < ApplicationController 2 | before_action :set_user, only: %i[show update destroy] 3 | 4 | # GET /users 5 | def index 6 | @users = User.all 7 | 8 | render json: @users 9 | end 10 | 11 | # GET /users/1 12 | def show 13 | render json: @user 14 | end 15 | 16 | # POST /users 17 | def create 18 | @user = User.new(user_params) 19 | 20 | if @user.save 21 | render json: @user, status: :created, location: @user 22 | else 23 | render json: @user.errors, status: :unprocessable_entity 24 | end 25 | end 26 | 27 | # PATCH/PUT /users/1 28 | def update 29 | if @user.update(user_params) 30 | render json: @user 31 | else 32 | render json: @user.errors, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | # DELETE /users/1 37 | def destroy 38 | @user.destroy 39 | end 40 | 41 | private 42 | 43 | # Use callbacks to share common setup or constraints between actions. 44 | def set_user 45 | @user = User.find(params[:id]) 46 | end 47 | 48 | # Only allow a trusted parameter "allow list" through. 49 | def user_params 50 | params.require(:user).permit(:name, :organization_id) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /docker/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /docker/app/controllers/healthcheck_controller.rb: -------------------------------------------------------------------------------- 1 | class HealthcheckController < ApplicationController 2 | skip_before_action :authenticate_user! 3 | skip_forgery_protection 4 | 5 | # 6 | # Respond to health checks 7 | # 8 | def index 9 | render plain: 'ok', status: :ok 10 | end 11 | 12 | # 13 | # Respond to smoke test status check 14 | # 15 | def show 16 | statuses = [ 17 | Job.count, 18 | Resource.count, 19 | Issue.count, 20 | Control.count 21 | ] 22 | 23 | status = statuses.all? { |s| s > 0 } 24 | 25 | response_code = status ? :ok : :service_unavailable 26 | 27 | render plain: response_code.to_s, status: response_code 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /docker/app/jobs/analysis_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pry' 4 | require 'rspec' 5 | require 'rspec/core' 6 | require 'rspec/core/formatters/base_formatter' 7 | require_relative 'spec/formatters/custom_formatter' 8 | require_relative 'spec/support/db_helper' 9 | require_relative 'spec/support/redisgraph_helper' 10 | require 'json' 11 | 12 | # 13 | # Runs all RSpec controls against current graph database 14 | # 15 | class AnalysisJob < ApplicationJob 16 | queue_as :default 17 | attr_accessor :output_hash 18 | 19 | TYPE = :analyze 20 | 21 | def perform(args) 22 | unless args.key?(:guid) 23 | logger.info 'Analysis job not running without a GUID.' 24 | return nil 25 | end 26 | 27 | # shared GUID 28 | guid = args[:guid] 29 | logger.info "#{guid} Analysis job started" 30 | 31 | # Track the job 32 | job = Job.create(status: :running, kind: TYPE, guid: guid) 33 | 34 | begin 35 | RSpec.world.reset 36 | RSpec.reset 37 | RSpec.clear_examples 38 | 39 | custom_formatter = CspmFormatter.new(StringIO.new) 40 | RSpec::Core::Reporter.new(custom_formatter) 41 | 42 | options = [] 43 | options << Dir['controls/**/controls_spec.rb'].reject { |f| f.starts_with?('controls/_') } 44 | options << '-fCspmFormatter' 45 | RSpec::Core::Runner.run(options) 46 | 47 | # results = JSON.parse(File.read(RunnerJob::RESULTS_FILE)) 48 | 49 | RSpec.clear_examples 50 | RSpec.world.reset 51 | RSpec.reset 52 | 53 | job.complete! 54 | puts "Analysis job finished - #{guid}" 55 | rescue StandardError => e 56 | job.failed! 57 | puts "#Analysis job failed - #{e.class} #{e.message} (#{e.backtrace[0]})" 58 | 59 | # re-raise for RunnerJob 60 | raise e 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /docker/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /docker/app/jobs/example_job.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | class ExampleJob < ApplicationJob 4 | queue_as :default 5 | 6 | def perform(*args) 7 | # Do something later 8 | logger.info "Job run" 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/batch_importer.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'config/config_loader' 4 | require 'fetcher/file_fetcher' 5 | require 'loader/file_loader' 6 | require 'redis' 7 | 8 | # Handles the batch import process by loading the config, 9 | # fetching the files, and dispatching them to the loader 10 | class BatchImporter 11 | attr_accessor :load_config 12 | 13 | def initialize(config_file) 14 | puts "Loading config file: #{config_file}" 15 | @load_config = ConfigLoader.new(config_file).loaded_config 16 | end 17 | 18 | # Fetch the remote bucket and local_dir files to a local 19 | # temp directory, and send the db connection info and an 20 | # array of files to the asset loader 21 | # Save redis contents to disk when done 22 | def import 23 | puts "Fetching files from remote buckets and local directories" 24 | files_to_load = FileFetcher.new(@load_config).fetch 25 | db_options = get_db_options(@load_config) 26 | FileLoader.new(db_options, files_to_load) 27 | Redis.new(db_options.to_h).call("save") 28 | end 29 | 30 | private 31 | 32 | # Return a default db conn config or what is configured 33 | # in the load_config under "db" 34 | def get_db_options(load_config) 35 | unless load_config.respond_to?(:db) 36 | db_hash = { 'url' => 'redis://localhost:6379' } 37 | return OpenStruct.new(db_hash) 38 | end 39 | 40 | load_config.delete_field('db') 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/config/config_loader.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'yaml' 4 | require 'json' 5 | require 'ostruct' 6 | 7 | # Loads the list of buckets into an Openstruct 8 | class ConfigLoader 9 | attr_accessor :loaded_config 10 | 11 | def initialize(config_file_path) 12 | @loaded_config = JSON.parse(YAML.load_file(config_file_path).to_json, object_class: OpenStruct) 13 | # TODO: use dry/schema to validate 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/db/redisgraph.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | # Returns a Graph DB connection 4 | module GraphDB 5 | # For RedisGraph 6 | module DB 7 | def db_connection(db_config) 8 | RedisGraph.new('opencspm', db_config.to_h) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/fetcher/file_fetcher.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'fetcher/types/bucket_fetcher' 4 | require 'fetcher/types/local_fetcher' 5 | 6 | # Fetches files from buckets to a local dir 7 | class FileFetcher 8 | attr_accessor :locations_to_fetch 9 | 10 | def initialize(locations_to_fetch) 11 | @locations_to_fetch = locations_to_fetch 12 | @remote_buckets = @locations_to_fetch.delete_field('buckets') if @locations_to_fetch.respond_to?(:buckets) 13 | @local_dirs = @locations_to_fetch.delete_field('local_dirs') if @locations_to_fetch.respond_to?(:local_dirs) 14 | @load_dir = File.expand_path('load_dir', Dir.pwd) 15 | end 16 | 17 | # Grabs from remote buckets to load_dir 18 | # Grabs from local dirs to load_dir 19 | # Returns an array of json files in load_dir 20 | def fetch 21 | clean_load_dir 22 | pull_from_remote_buckets(@remote_buckets) 23 | pull_from_local_dirs(@local_dirs) 24 | load_dir_json_files 25 | end 26 | 27 | private 28 | 29 | # Clean up load dir where this job will fetch new files 30 | # from all locations (buckets and local fs dirs) 31 | # and place them in to be loaded/parsed 32 | def clean_load_dir 33 | puts "Cleaning #{@load_dir}" 34 | FileUtils.rm_rf(Dir.glob("#{@load_dir}/*.json")) 35 | end 36 | 37 | # Pull buckets to load_dir 38 | def pull_from_remote_buckets(buckets) 39 | return if buckets.nil? 40 | BucketFetcher.new(buckets, @load_dir).fetch 41 | end 42 | 43 | # Pull local_dirs to load_dir 44 | def pull_from_local_dirs(local_dirs) 45 | return if local_dirs.nil? 46 | LocalFetcher.new(local_dirs, @load_dir).fetch 47 | end 48 | 49 | # Return list of files from local dir 50 | def load_dir_json_files 51 | Dir["#{@load_dir}/combined_for_load.json"] 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/fetcher/types/local_fetcher.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | class LocalFetcher 4 | def initialize(local_dirs, load_dir) 5 | @local_dirs = local_dirs 6 | @load_dir = load_dir 7 | end 8 | 9 | def fetch 10 | puts @local_dirs 11 | # Enumerate **/*manifest.txt and /*manifest.txt 12 | @local_dirs.each do |local_dir| 13 | dirs = Dir["#{local_dir}/*/*manifest.txt"] + Dir["#{local_dir}/*manifest.txt"] 14 | dirs.each do |manifest_file| 15 | manifest_base_dir = File.dirname(manifest_file) 16 | cai_files = [] 17 | begin 18 | File.readlines(manifest_file).each do |cai_file| 19 | source_file_path = "#{manifest_base_dir}/#{cai_file}".chomp 20 | file_data = File.read(source_file_path) 21 | dest_file_path = "#{@load_dir}/combined_for_load.json" 22 | puts "Appending #{source_file_path} to #{dest_file_path}" 23 | File.write(dest_file_path, file_data, mode: "ab") 24 | end 25 | rescue => e 26 | puts e.inspect 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/asset_loader.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require_relative 'utils/enumerable' 4 | 5 | # Base abstract class for assets to be loaded 6 | class AssetLoader 7 | def initialize; end 8 | 9 | def load 10 | raise NotImplementedEror 11 | end 12 | 13 | private 14 | 15 | # sanitizes json values 16 | # TODO: utils instead 17 | def sanitize_value(value) 18 | value = CGI.escape(value) if value.start_with?('{') 19 | value&.gsub(%r{^//}, '') 20 | end 21 | 22 | def graphquery(query, _params = {}) 23 | #print '.' 24 | begin 25 | @db.query(query) 26 | rescue RedisGraph::QueryError => e 27 | print "Query failure. Check /tmp/failedqueries.txt" 28 | open('/tmp/failedqueries.txt', 'a') do |f| 29 | f.puts "QueryError: #{e}" 30 | f.puts "\n" 31 | f.puts query 32 | f.puts "\n" 33 | f.puts "\n" 34 | end 35 | end 36 | end 37 | 38 | def fetch_current_properties_as_null(asset_label, asset_name) 39 | current_properties = graphquery("match(n:#{asset_label} { name: \"#{asset_name}\" }) return (n)").resultset 40 | return '' if current_properties == [] 41 | 42 | # rubocop:disable Layout/LineLength 43 | current_properties = current_properties.first.first.reduce({}, :merge).tap { |hs| hs.delete('name') }.tap { |hs| hs.delete('asset_type') }.map { |h| "a.#{h[0]} = NULL" }.join ', ' 44 | # rubocop:enable Layout/LineLength 45 | 46 | return '' if current_properties == '' 47 | 48 | current_properties + ",\n" 49 | end 50 | 51 | def prepare_properties(asset) 52 | # rubocop:disable Layout/LineLength 53 | asset.flatten_with_path.tap { |hs| hs.delete('name') }.tap { |hs| hs.delete('asset_type') }.first(300).map { |h| "a.#{h[0]} = \"#{h[1].to_s.slice(0..500).gsub(/\"/, '\\"').gsub(/\\\\"/, '\\"').gsub(/\n/,'\n')}\"" }.join ",\n" 54 | # rubocop:enable Layout/LineLength 55 | end 56 | 57 | def prepare_relationship_properties(properties_hash) 58 | properties_hash.map { |h| "#{h[0]}: \"#{h[1]}\"" }.join ",\n" 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Shim to load each individual AWS service loaders 3 | # 4 | module AWSLoader 5 | end 6 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/_resource_loader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # AWS asset graph db loader 5 | # 6 | class AWSLoader::ResourceLoader 7 | def initialize(asset, db, import_id) 8 | @asset = asset 9 | @db = db 10 | @import_id = import_id 11 | 12 | load 13 | end 14 | 15 | def load 16 | # re-parse as JSON to get recursive OpenStruct's 17 | json = JSON.parse(@asset.to_json, object_class: OpenStruct) 18 | 19 | # set import_id 20 | json.import_id = @import_id 21 | 22 | # Instantiate a Loader instance for each service type 23 | begin 24 | svc = "::AWSLoader::#{json.service}" 25 | aws_loader = Object.const_get(svc) 26 | loader = aws_loader.new(json) 27 | 28 | # DEBUG: skip loader methods that aren't implemented yet 29 | unless loader.respond_to?(json.asset_type) 30 | puts "\nWarning: no #{json.service} loader defined for asset type: \x1b[35m#{json.asset_type}\x1b[0m" 31 | return 32 | end 33 | 34 | # call loader method for the asset type 35 | loader.send(json.asset_type)&.each do |q| 36 | @db.query(q) 37 | printf "\x1b[32m.\x1b[0m" if ENV['SHOW_DOTS'] 38 | end 39 | rescue NameError => e 40 | # Instances of ::GraphDbLoader will raise a NameError exception 41 | # if the individual methods try to call a non-existent field 42 | # returned from the AWS API. We don't want to swallow that 43 | # exception, so re-raise it here. 44 | 45 | # raise e if e.receiver.nil? 46 | 47 | # raise all 48 | raise e 49 | 50 | # puts "No loader defined for service: #{json.service}" 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/accessanalyzer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load AccessAnalyzer assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::AccessAnalyzer < GraphDbLoader 9 | def analyzer 10 | node = 'AWS_ACCESS_ANALYZER' 11 | q = [] 12 | 13 | # analyzer node 14 | q.push(_upsert({ node: node, id: @name })) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/acm.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load ACM assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::ACM < GraphDbLoader 7 | def certificate 8 | node = 'AWS_CERTIFICATE' 9 | q = [] 10 | 11 | # certificate node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | # certificate details 15 | if @data.details 16 | opts = { 17 | node: node, 18 | id: @name, 19 | data: @data.details 20 | } 21 | 22 | q.push(_append(opts)) 23 | end 24 | 25 | q 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/apigateway.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load APIGateway assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::APIGateway < GraphDbLoader 7 | def api 8 | node = 'AWS_API_GATEWAY' 9 | q = [] 10 | 11 | # api node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | # TODO: link to AWS_CERTIFICATE 15 | # TODO: link to AWS_API_GATEWAY_STAGE 16 | end 17 | 18 | def domain 19 | node = 'AWS_API_GATEWAY_DOMAIN' 20 | q = [] 21 | 22 | # domain node 23 | q.push(_upsert({ node: node, id: @name })) 24 | 25 | # TODO: link to AWS_API_GATEWAY 26 | # TODO: link to AWS_CERTIFICATE 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/apigatewayv2.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load ApiGatewayV2 assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::ApiGatewayV2 < RedisGraph 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/applicationautoscaling.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load ApplicationAutoScaling assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::ApplicationAutoScaling < RedisGraph 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/athena.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load Athena assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::Athena < GraphDbLoader 7 | def workgroup 8 | node = 'AWS_ATHENA_WORKGROUP' 9 | q = [] 10 | 11 | # workgroup node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | # workgroup details 15 | if @data&.details&.work_group 16 | opts = { 17 | node: node, 18 | id: @name, 19 | # TODO: improve mapping of work_group details (field name conflicts) 20 | data: { 21 | work_group_name: @data.details.work_group.name, 22 | work_group_state: @data.details.work_group.state 23 | } 24 | } 25 | 26 | q.push(_append(opts)) 27 | end 28 | 29 | q 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/autoscaling.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load AutoScaling assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::AutoScaling < GraphDbLoader 7 | def auto_scaling_group 8 | node = 'AWS_AUTO_SCALING_GROUP' 9 | q = [] 10 | 11 | # asg node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | # TODO: map instances to AWS_INSTANCEs 15 | # TODO: map availability_zones to AWS_AVAILABILITY_ZONEs 16 | # TODO: map vpc_zone_identifier to AWS_SUBNETs 17 | # TODO: map tags to AWS_TAGs 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/backup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Backup assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Backup < GraphDbLoader 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/cloudformation.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load CloudFormation assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::CloudFormation < GraphDbLoader 7 | def stack 8 | node = 'AWS_CLOUDFORMATION_STACK' 9 | q = [] 10 | 11 | # stack node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | # TODO: map parameters 15 | # TODO: map outputs 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/cloudfront.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load CloudFront assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::CloudFront < GraphDbLoader 7 | def distribution 8 | node = 'AWS_CLOUDFRONT_DISTRIBUTION' 9 | q = [] 10 | 11 | # distribution node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | # distribution details 15 | if @data.details 16 | opts = { 17 | node: node, 18 | id: @name, 19 | data: @data.details 20 | } 21 | 22 | q.push(_append(opts)) 23 | end 24 | 25 | # TODO: map origins 26 | # TODO: map aliases 27 | # TODO: map cache behaviors 28 | # TODO: map certificates 29 | 30 | q 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/cloudwatch.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load CloudWatch assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::CloudWatch < GraphDbLoader 7 | def metric_alarm 8 | node = 'AWS_CLOUDWATCH_METRIC_ALARM' 9 | q = [] 10 | 11 | # metric_alarm node 12 | q.push(_upsert({ node: node, id: @name })) 13 | q 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/cloudwatchlogs.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load CloudWatchLogs assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::CloudWatchLogs < GraphDbLoader 7 | def log_group 8 | node = 'AWS_CLOUDWATCH_LOG_GROUP' 9 | q = [] 10 | 11 | # log_group node 12 | q.push(_upsert({ node: node, id: @name })) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/codebuild.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load CodeBuild assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::CodeBuild < RedisGraph 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/codepipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load CodePipeline assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::CodePipeline < RedisGraph 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/configservice.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load ConfigService assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::ConfigService < GraphDbLoader 9 | def rule 10 | node = 'AWS_CONFIG_RULE' 11 | q = [] 12 | 13 | # rule node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | q 17 | end 18 | 19 | def delivery_channel 20 | node = 'AWS_CONFIG_DELIVERY_CHANNEL' 21 | q = [] 22 | 23 | # delivery_channel node 24 | q.push(_upsert({ node: node, id: @name })) 25 | 26 | q 27 | end 28 | 29 | def configuration_recorder 30 | node = 'AWS_CONFIG_CONFIGURATION_RECORDER' 31 | q = [] 32 | 33 | # configuration_recorder node 34 | q.push(_upsert({ 35 | node: node, 36 | id: @name, 37 | data: {}.merge(@data.recording_group.to_h).merge(@data.status.to_h) 38 | })) 39 | 40 | q 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/databasemigrationservice.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load DatabaseMigrationService assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::DatabaseMigrationService < GraphDbLoader 7 | def replication_instance 8 | node = 'AWS_DMS_REPLICATION_INSTANCE' 9 | q = [] 10 | 11 | # replication_instance node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | q 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/directoryservice.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load DirectoryService assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::DirectoryService < GraphDbLoader 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/dynamodb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load DynamoDB assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::DynamoDB < GraphDbLoader 9 | def table 10 | node = 'AWS_DYNAMODB_TABLE' 11 | q = [] 12 | 13 | # table node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | # encryption attributes 17 | q.push(_append({ node: node, id: @name, data: { 18 | sse_status: @data&.sse_description&.status || 'none', 19 | sse_type: @data&.sse_description&.sse_type || 'none' 20 | } })) 21 | 22 | q 23 | end 24 | 25 | def limits 26 | node = 'AWS_DYNAMODB_LIMITS' 27 | q = [] 28 | 29 | # limits node 30 | q.push(_upsert({ node: node, id: @name })) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/ecr.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load ECR assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::ECR < GraphDbLoader 7 | def repository 8 | node = 'AWS_ECR_REPOSITORY' 9 | q = [] 10 | 11 | # repository node 12 | q.push(_upsert({ node: node, id: @data.arn })) 13 | 14 | # repository scan_on_push and encryption status 15 | if @data.image_scanning_configuration && @data.encryption_configuration 16 | opts = { 17 | node: node, 18 | id: @name, 19 | data: { 20 | scan_on_push: @data.image_scanning_configuration.scan_on_push, 21 | encryption_type: @data.encryption_configuration.encryption_type 22 | } 23 | } 24 | 25 | q.push(_append(opts)) 26 | end 27 | 28 | q 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/efs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load EFS assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::EFS < GraphDbLoader 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/eks.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load EKS assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::EKS < GraphDbLoader 7 | def cluster 8 | node = 'AWS_EKS_CLUSTER' 9 | q = [] 10 | 11 | # instance node 12 | q.push(_upsert({ node: node, id: @data.arn })) 13 | 14 | # vpc node and relationship 15 | if @data.resources_vpc_config&.vpc_id 16 | opts = { 17 | parent_node: node, 18 | parent_name: @data.arn, 19 | child_node: 'AWS_VPC', 20 | child_name: @data.resources_vpc_config.vpc_id, 21 | relationship: 'MEMBER_OF_VPC' 22 | } 23 | 24 | q.push(_upsert_and_link(opts)) 25 | end 26 | 27 | # subnets and relationship 28 | @data.resources_vpc_config&.subnet_ids&.each do |subnet_id| 29 | opts = { 30 | parent_node: node, 31 | parent_name: @data.arn, 32 | child_node: 'AWS_SUBNET', 33 | child_name: subnet_id, 34 | relationship: 'IN_SUBNET' 35 | } 36 | 37 | q.push(_upsert_and_link(opts)) 38 | end 39 | 40 | # TODO: nodegroups 41 | 42 | # TODO: nodes 43 | 44 | # logging types 45 | @data.logging&.cluster_logging&.each do |logging| 46 | logging.types.each do |logging_type| 47 | opts = { 48 | parent_node: 'AWS_EKS_CLUSTER', 49 | parent_name: @data.arn, 50 | child_node: 'AWS_EKS_CLUSTER_LOGGING_TYPE', 51 | child_name: logging_type, 52 | relationship: 'HAS_LOGGING_TYPE', 53 | relationship_attributes: { enabled: logging.enabled.to_s }, 54 | headless: true 55 | } 56 | 57 | q.push(_upsert_and_link(opts)) 58 | end 59 | end 60 | 61 | q 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/elasticache.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load ElastiCache assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::ElastiCache < GraphDbLoader 7 | def cluster 8 | node = 'AWS_ELASTICACHE_CLUSTER' 9 | q = [] 10 | 11 | # cluster node 12 | q.push(_upsert({ node: node, id: @name })) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/elasticsearch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load ElasticsearchService assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::ElasticsearchService < GraphDbLoader 9 | def domain 10 | node = 'AWS_ELASTICSEARCH_DOMAIN' 11 | q = [] 12 | 13 | # domain node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | # additional attributes 17 | vpc_id = @data&.vpc_options&.vpc_id 18 | enforce_https = @data&.domain_endpoint_options&.enforce_https || false 19 | encryption_at_rest = @data&.encryption_at_rest_options&.enabled || false 20 | node_to_node = @data&.node_to_node_encryption_options&.enabled || false 21 | log_errors = @data&.log_publishing_options&.ES_APPLICATION_LOGS&.enabled || false 22 | log_index_slow = @data&.log_publishing_options&.INDEX_SLOW_LOGS&.enabled || false 23 | log_search_slow = @data&.log_publishing_options&.SEARCH_SLOW_LOGS&.enabled || false 24 | cognito_options = @data&.cognito_options&.enabled || false 25 | 26 | q.push(_append({ node: node, id: @name, 27 | data: { 28 | vpc_id: vpc_id, 29 | enforce_https_enabled: enforce_https, 30 | encryption_at_rest_enabled: encryption_at_rest, 31 | node_to_node_encryption_options: node_to_node, 32 | cognito_options_enabled: cognito_options, 33 | log_errors: log_errors, 34 | log_index_slow: log_index_slow, 35 | log_search_slow: log_search_slow 36 | }})) 37 | 38 | q 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/elb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load ElasticLoadBalancing (v1) assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::ElasticLoadBalancing < GraphDbLoader 9 | def load_balancer 10 | node = 'AWS_ELASTIC_LOAD_BALANCER' 11 | q = [] 12 | 13 | # load_balancer node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | # logging enabled? 17 | logging_enabled = @data&.attributes&.access_log&.enabled || false 18 | 19 | # append logging attributes 20 | q.push(_append({ node: node, id: @name, data: { logging_enabled: logging_enabled } })) 21 | 22 | @data&.listener_descriptions&.each_with_index do |listener, i| 23 | opts = { 24 | parent_node: node, 25 | parent_name: @name, 26 | child_node: 'AWS_ELASTIC_LOAD_BALANCER_LISTENER', 27 | child_name: "#{@data.dns_name}/listener-#{i}", 28 | relationship: 'HAS_LISTENER', 29 | relationship_attributes: { 30 | port: listener.listener.load_balancer_port, 31 | protocol: listener.listener.protocol.downcase, 32 | ssl_certificate_id: listener.listener.ssl_certificate_id 33 | }, 34 | headless: true 35 | } 36 | 37 | q.push(_upsert_and_link(opts)) 38 | end 39 | 40 | q 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/emr.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load EMR assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::EMR < GraphDbLoader 9 | def configuration 10 | node = 'AWS_EMR_CONFIGURATION' 11 | q = [] 12 | 13 | # block_public_access_configuration node 14 | q.push(_upsert({ node: node, id: @name })) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/esc.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load ECS assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::ECS < GraphDbLoader 7 | def cluster 8 | node = 'AWS_ECS_CLUSTER' 9 | q = [] 10 | 11 | # cluster node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | q 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/firehose.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load Firehose assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::Firehose < GraphDbLoader 7 | def stream 8 | node = 'AWS_FIREHOSE_STREAM' 9 | q = [] 10 | 11 | # stream node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | q 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/guardduty.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load GuardDuty assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::GuardDuty < GraphDbLoader 9 | def detector 10 | node = 'AWS_GUARDDUTY_DETECTOR' 11 | q = [] 12 | 13 | # detector node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | # detector findings statistics 17 | q.push(_append({ node: node, id: @name, data: { 18 | findings: @data&.findings_statistics&.count_by_severity&.to_h&.values&.inject(:+) || 0, 19 | findings_aged_short: @data&.findings_statistics_aged_short&.count_by_severity&.to_h&.values&.inject(:+) || 0, 20 | findings_aged_long: @data&.findings_statistics_aged_long&.count_by_severity&.to_h&.values&.inject(:+) || 0 21 | } })) 22 | 23 | q 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/iam/group.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load AWS_IAM_GROUP nodes 3 | # 4 | class AWSLoader::IAM < GraphDbLoader 5 | def group 6 | node = 'AWS_IAM_GROUP' 7 | q = [] 8 | 9 | # group node 10 | q.push(_upsert({ node: node, id: @name })) 11 | 12 | # TODO: map attached_managed_policies 13 | q 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/iam/role.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load AWS_IAM_ROLE nodes 3 | # 4 | class AWSLoader::IAM < GraphDbLoader 5 | def role 6 | node = 'AWS_IAM_ROLE' 7 | q = [] 8 | 9 | # role node 10 | q.push(_upsert({ node: node, id: @name })) 11 | 12 | # TODO: map attached_managed_policies 13 | q 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/iam/server_certificate.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load AWS_IAM_SERVER_CERTIFICATE nodes 3 | # 4 | class AWSLoader::IAM < GraphDbLoader 5 | def server_certificate 6 | node = 'AWS_IAM_SERVER_CERTIFICATE' 7 | q = [] 8 | 9 | # server_certificate node 10 | q.push(_upsert({ node: node, id: @name })) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/iam/virtual_mfa_device.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load AWS_IAM_MFA_DEVICE nodes 3 | # 4 | class AWSLoader::IAM < GraphDbLoader 5 | def virtual_mfa_device 6 | node = 'AWS_IAM_MFA_DEVICE' 7 | q = [] 8 | 9 | # virtual_mfa_device node 10 | q.push(_upsert({ node: node, id: @name })) 11 | 12 | # user node and relationship 13 | if @data.user 14 | opts = { 15 | parent_node: node, 16 | parent_name: @name, 17 | child_node: 'AWS_IAM_USER', 18 | child_name: @data.user.arn, 19 | relationship: 'HAS_MFA_DEVICE', 20 | relationship_attributes: { virtual_mfa_token: true } 21 | } 22 | 23 | q.push(_upsert_and_link(opts)) 24 | end 25 | 26 | q 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/kafka.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load Kafka assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::Kafka < GraphDbLoader 7 | def cluster 8 | node = 'AWS_KAFKA_CLUSTER' 9 | q = [] 10 | 11 | # cluster node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | q 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/kinesis.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load Kinesis assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::Kinesis < GraphDbLoader 7 | def stream 8 | node = 'AWS_KINESIS_STREAM' 9 | q = [] 10 | 11 | # stream node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | q 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/kms.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load KMS assets into RedisGraph 3 | # 4 | # Each method returns an array of Cypher queries 5 | # 6 | class AWSLoader::KMS < GraphDbLoader 7 | def key 8 | node = 'AWS_KMS_KEY' 9 | q = [] 10 | 11 | # key node 12 | q.push(_upsert({ node: node, id: @name })) 13 | 14 | # TODO: filter out policy field 15 | 16 | q 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/organizations.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Organizations assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Organizations < GraphDbLoader 9 | def organization 10 | node = 'AWS_ORGANIZATION' 11 | q = [] 12 | 13 | account_id = "arn:aws:::#{@account}/account" 14 | 15 | # organization node 16 | q.push(_upsert({ node: node, id: @name })) 17 | 18 | # account node 19 | q.push(_upsert({ node: 'AWS_ACCOUNT', id: account_id })) 20 | 21 | # account -> organization 22 | opts = { 23 | from_node: 'AWS_ORGANIZATION', 24 | from_name: @name, 25 | to_node: 'AWS_ACCOUNT', 26 | to_name: account_id, 27 | relationship: 'MEMBER_OF_ORGANIZATION' 28 | } 29 | 30 | q.push(_link(opts)) 31 | 32 | q 33 | end 34 | 35 | def handshake 36 | node = 'AWS_ORGANIZATION_HANDSHAKE' 37 | q = [] 38 | 39 | # handshake node 40 | q.push(_upsert({ node: node, id: @name })) 41 | 42 | q 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/redshift.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Redshift assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Redshift < RedisGraph 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/route53.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Route53 assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Route53 < GraphDbLoader 9 | def zone 10 | node = 'AWS_ROUTE53_ZONE' 11 | q = [] 12 | 13 | # zone node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | q.push(_append({ node: node, id: @name, data: { 17 | cloud_watch_logs_log_group_arn: @data&.logging_config&.cloud_watch_logs_log_group_arn || 'none' 18 | } })) 19 | 20 | q 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/route53domains.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Route53Domains assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Route53Domains < GraphDbLoader 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/sagemaker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load SageMaker assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::SageMaker < GraphDbLoader 9 | def notebook_instance 10 | node = 'AWS_SAGEMAKER_NOTEBOOK' 11 | q = [] 12 | 13 | # notebook node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | q 17 | end 18 | 19 | def endpoint 20 | node = 'AWS_SAGEMAKER_ENDPOINT' 21 | q = [] 22 | 23 | # endpoint node 24 | q.push(_upsert({ node: node, id: @name })) 25 | 26 | # data_capture_config 27 | if @data.data_capture_config 28 | node = 'AWS_SAGEMAKER_DATA_CAPTURE_CONFIG' 29 | name = "#{@name}/data_capture_config" 30 | 31 | opts = { 32 | parent_node: 'AWS_SAGEMAKER_ENDPOINT', 33 | parent_name: @name, 34 | child_node: node, 35 | child_name: name, 36 | relationship: 'HAS_DATA_CAPTURE_CONFIG', 37 | relationship_attributes: { enabled: @data.data_capture_config.enable_capture.to_s }, 38 | headless: true 39 | } 40 | 41 | # create a headless data_capture_config node 42 | q.push(_upsert_and_link(opts)) 43 | 44 | # then add the data_capture_config attributes 45 | q.push(_append({ 46 | node: node, 47 | id: name, 48 | data: @data.data_capture_config 49 | })) 50 | end 51 | 52 | q 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/secretsmanager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load SecretsManager assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::SecretsManager < GraphDbLoader 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/securityhub.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load SecurityHub assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::SecurityHub < GraphDbLoader 9 | def hub 10 | node = 'AWS_SECURITYHUB_HUB' 11 | q = [] 12 | 13 | # hub node 14 | q.push(_upsert({ node: node, id: @name })) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/servicequotas.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load ServiceQuota assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::ServiceQuotas < GraphDbLoader 9 | def quota 10 | node = 'AWS_SERVICE_QUOTA' 11 | q = [] 12 | 13 | # quota node 14 | q.push(_upsert({ node: node, id: @name })) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/ses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load SES assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::SES < GraphDbLoader 9 | def identity 10 | node = 'AWS_SES_IDENTITY' 11 | q = [] 12 | 13 | # identity node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | # dkim attributes 17 | q.push(_append({ node: node, id: @name, data: { 18 | dkim_enabled: @data&.dkim_attributes&.dkim_enabled || false, 19 | dkim_verification_status: @data&.dkim_attributes&.dkim_verification_status 20 | } })) 21 | q 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/shield.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Shield assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Shield < GraphDbLoader 9 | def subscription 10 | node = 'AWS_SHIELD_SUBSCRIPTION' 11 | 12 | q = [] 13 | 14 | # subscription node 15 | q.push(_upsert({ node: node, id: @name })) 16 | end 17 | 18 | def contact_list 19 | node = 'AWS_SHIELD_CONTACT_LIST' 20 | 21 | q = [] 22 | 23 | # contact_list node 24 | q.push(_upsert({ node: node, id: @name })) 25 | 26 | # contacts 27 | @data&.contacts&.each_with_index do |contact, i| 28 | contact_id = "#{@name}/contact_#{i}" 29 | 30 | opts = { 31 | node: 'AWS_SHIELD_CONTACT_LIST_CONTACT', 32 | id: contact_id, 33 | data: { 34 | email: contact.email_address, 35 | phone: contact.phone_number 36 | }, 37 | headless: true 38 | } 39 | 40 | q.push(_upsert(opts)) 41 | 42 | # contact_list -> contact 43 | opts = { 44 | from_node: 'AWS_SHIELD_CONTACT_LIST', 45 | from_name: @name, 46 | to_node: 'AWS_SHIELD_CONTACT_LIST_CONTACT', 47 | to_name: contact_id, 48 | relationship: 'HAS_CONTACT' 49 | } 50 | 51 | q.push(_link(opts)) 52 | end 53 | 54 | q 55 | end 56 | 57 | def protection 58 | node = 'AWS_SHIELD_PROTECTION' 59 | q = [] 60 | 61 | # protection node 62 | q.push(_upsert({ node: node, id: @name })) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/ssm.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load SSM assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::SSM < GraphDbLoader 9 | def instance 10 | node = 'AWS_SSM_INSTANCE' 11 | q = [] 12 | 13 | # instance node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | # instance -> ssm_instance 17 | opts = { 18 | from_node: 'AWS_E2_INSTANCE', 19 | from_name: @data.instance_id, 20 | to_node: node, 21 | to_name: @name, 22 | relationship: 'HAS_SSM_AGENT', 23 | relationship_attributes: { 24 | association_status: @data.association_status, 25 | ping_status: @data.ping_status 26 | }, 27 | headless: true 28 | } 29 | 30 | q.push(_link(opts)) 31 | 32 | q 33 | end 34 | 35 | def parameter 36 | node = 'AWS_SSM_PARAMETER' 37 | q = [] 38 | 39 | # parameter node 40 | q.push(_upsert({ node: node, id: @name })) 41 | 42 | q 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Support assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Support < GraphDbLoader 9 | def trusted_advisor_check 10 | node = 'AWS_TRUSTED_ADVISOR_CHECK' 11 | q = [] 12 | 13 | # trusted_advisor_check node 14 | q.push(_upsert({ node: node, id: @name })) 15 | 16 | q 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/transfer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load Transfer assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::Transfer < GraphDbLoader 9 | def server 10 | node = 'AWS_TRANSFER_SERVER' 11 | q = [] 12 | 13 | # server node 14 | q.push(_upsert({ node: node, id: @name })) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/workspaces.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load WorkSpaces assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::WorkSpaces < RedisGraph 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/aws_loader/xray.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Load XRay assets into RedisGraph 5 | # 6 | # Each method returns an array of Cypher queries 7 | # 8 | class AWSLoader::XRay < GraphDbLoader 9 | def config 10 | node = 'AWS_XRAY_CONFIG' 11 | q = [] 12 | 13 | # block_public_access_configuration node 14 | q.push(_upsert({ node: node, id: @name })) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/gcp_loader/gcp_compute_network.rb: -------------------------------------------------------------------------------- 1 | class GCP_COMPUTE_NETWORK < GCPLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | # set defaults 9 | peerings = [] 10 | # grab/delete associated networks 11 | if asset.dig('resource', 'data', 'peerings') 12 | peerings = asset.dig('resource', 'data').delete('peerings') 13 | end 14 | # grab/delete alternative nameservers 15 | if asset.dig('resource','data','subnetworks') 16 | asset.dig('resource','data').delete('subnetworks') 17 | end 18 | # cleanup 19 | asset.delete('ancestors') 20 | 21 | # prep for updating or creating 22 | previous_properties_as_null = fetch_current_properties_as_null(@asset_label, @asset_name) 23 | properties = prepare_properties(asset) 24 | query = """ 25 | MERGE (a:#{@asset_label} { name: \"#{@asset_name}\" }) 26 | ON CREATE SET a.asset_type = \"#{@asset_type}\", a.last_updated = #{@import_id}, a.loader_type = \"gcp\", #{properties} 27 | ON MATCH SET #{previous_properties_as_null} a.asset_type = \"#{@asset_type}\", a.last_updated = #{@import_id}, a.loader_type = \"gcp\", #{properties} 28 | """ 29 | graphquery(query) 30 | 31 | # Relationships to networks 32 | peerings.each do |peering| 33 | compute_network_name = compute_url_to_compute_name(peering['network']) 34 | query = """ 35 | MATCH (d:#{@asset_label} { name: \"#{@asset_name}\" }) 36 | MERGE (a:GCP_COMPUTE_NETWORK { name: \"#{compute_network_name}\" }) 37 | ON CREATE SET a.asset_type = \"compute.googleapis.com/Network\", a.last_updated = #{@import_id}, a.loader_type = \"gcp\" 38 | ON MATCH SET a.asset_type = \"compute.googleapis.com/Network\", a.last_updated = #{@import_id}, a.loader_type = \"gcp\" 39 | MERGE (d)-[:IS_VPCPEER]->(a) 40 | """ 41 | graphquery(query) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/gcp_loader/gcp_container_nodepool.rb: -------------------------------------------------------------------------------- 1 | class GCP_CONTAINER_NODEPOOL < GCPLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | # skipped because it's obtained from GCP_CONTAINER_CLUSTER 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/gcp_loader/gcp_default.rb: -------------------------------------------------------------------------------- 1 | class GCP_DEFAULT < GCPLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | return if @asset_label == 'GCP_CONTAINERREGISTRY_IMAGE' 10 | # Remove ancestors 11 | asset.delete('ancestors') 12 | 13 | # create/update gcp asset 14 | previous_properties_as_null = fetch_current_properties_as_null(@asset_label, @asset_name) 15 | properties = prepare_properties(asset) 16 | query = """ 17 | MERGE (a:#{@asset_label} { name: \"#{@asset_name}\" }) 18 | ON CREATE SET a.asset_type = \"#{@asset_type}\", 19 | a.last_updated = #{@import_id}, 20 | a.loader_type = \"gcp\", 21 | #{properties} 22 | ON MATCH SET #{previous_properties_as_null} 23 | a.asset_type = \"#{@asset_type}\", 24 | a.last_updated = #{@import_id}, 25 | a.loader_type = \"gcp\", 26 | #{properties} 27 | """ 28 | graphquery(query) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/gcp_loader/gcp_iam_serviceaccount.rb: -------------------------------------------------------------------------------- 1 | class GCP_IAM_SERVICEACCOUNT < GCPLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | # modify to GCP_IDENTITY 9 | @asset_label = 'GCP_IDENTITY' 10 | sa_email = asset.dig('resource', 'data', 'email') 11 | @asset_name = "serviceAccount:#{sa_email}" 12 | # create/update gcp asset 13 | previous_properties_as_null = fetch_current_properties_as_null(@asset_label, @asset_name) 14 | # TODO Deep parsing 15 | properties = prepare_properties(asset) 16 | query = """ 17 | MERGE (a:#{@asset_label} { name: \"#{@asset_name}\" }) 18 | ON CREATE SET a.asset_type = \"#{@asset_type}\", a.last_updated = #{@import_id}, a.member_type = \"serviceAccount\", a.member_name = \"#{sa_email}\", a.loader_type = \"gcp\", #{properties} 19 | ON MATCH SET #{previous_properties_as_null} a.asset_type = \"#{@asset_type}\", a.last_updated = #{@import_id}, a.member_type = \"serviceAccount\", a.member_name = \"#{sa_email}\", a.loader_type = \"gcp\", #{properties} 20 | """ 21 | graphquery(query) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_configmap.rb: -------------------------------------------------------------------------------- 1 | class K8S_CONFIGMAP < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | # Chop down configmap values 10 | if asset.dig('resource','data','data') 11 | asset.dig('resource','data','data').keys.each do |key| 12 | asset['resource']['data']['data'][key] = asset['resource']['data']['data'][key][0..100] 13 | end 14 | end 15 | k8s_resource_upsert(asset, @asset_type, @asset_label, @asset_name) 16 | 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_job.rb: -------------------------------------------------------------------------------- 1 | class K8S_JOB < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | job_conditions = [] 10 | if asset.dig('resource','data','status','conditions') 11 | job_conditions = asset.dig('resource', 'data', 'status').delete('conditions') 12 | end 13 | 14 | k8s_resource_upsert(@asset, @asset_type, @asset_label, @asset_name) 15 | 16 | unless job_conditions.nil? 17 | job_conditions.each do |jc| 18 | jc_name = jc['type'] || 'Unnamed' 19 | jc.delete('type') 20 | supporting_relationship_with_attrs("K8S_JOB", @asset_name, "K8S_JOBCONDITION", jc_name, "k8s.io/JobCondition", {}, "k8s", "HAS_K8SJOBCONDITION", jc, "left") 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_limitrange.rb: -------------------------------------------------------------------------------- 1 | class K8S_LIMITRANGE < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | limitrange_items = [] 10 | if asset.dig('resource','data','spec','limits') 11 | limitrange_items = asset.dig('resource', 'data', 'spec').delete('limits') 12 | end 13 | 14 | k8s_resource_upsert(@asset, @asset_type, @asset_label, @asset_name) 15 | 16 | unless limitrange_items.nil? 17 | limitrange_items.each do |lr| 18 | lr_name = lr['type'] || 'Unnamed' 19 | lr_name = "#{@asset_name}/#{lr_name}" 20 | lr.delete('type') 21 | supporting_relationship_with_attrs("K8S_LIMITRANGE", @asset_name, "K8S_LIMITRANGEITEM", lr_name, "k8s.io/LimitRangeItem", lr, "k8s", "HAS_K8SLIMITRANGEITEM", {}, "left") 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_node.rb: -------------------------------------------------------------------------------- 1 | class K8S_NODE < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | node_status_conditions = @asset.dig('resource', 'data', 'status', 'conditions') 10 | node_images = @asset.dig('resource', 'data', 'status', 'images') 11 | unless node_status_conditions.nil? 12 | @asset.dig('resource', 'data', 'status').delete('conditions') 13 | end 14 | unless node_images.nil? 15 | @asset.dig('resource', 'data', 'status').delete('images') 16 | end 17 | 18 | k8s_resource_upsert(@asset, @asset_type, @asset_label, @asset_name) 19 | 20 | # Maps the K8S_NODE to the corresponding GCP or AWS Instance Resource 21 | if @asset_name.match(/^container.googleapis.com/) 22 | project_id = @asset_name.match(/^container.googleapis.com\/projects\/([a-zA-Z0-9\-_]*)\//)[1] 23 | zone = @asset.dig('resource', 'data', 'metadata', 'labels', 'failure-domain.beta.kubernetes.io/zone') 24 | instance_name = @asset.dig('resource', 'data', 'metadata', 'name') 25 | unless instance_name.nil? && project_id.nil? && zone.nil? 26 | gce_instance_name = "compute.googleapis.com/projects/#{project_id}/zones/#{zone}/instances/#{instance_name}" 27 | supporting_relationship("K8S_NODE", @asset_name, "GCP_COMPUTE_INSTANCE", gce_instance_name, "compute.googleapis.com/Instance", "gcp", "HAS_K8SNODE", "right") 28 | end 29 | end 30 | 31 | unless node_status_conditions.nil? 32 | node_status_conditions.each do |condition| 33 | condition_type = condition['type'] 34 | condition.delete('type') 35 | supporting_relationship_with_attrs("K8S_NODE", @asset_name, "K8S_NODECONDITION", condition_type, "k8s.io/NodeCondition", {}, "k8s", "HAS_K8SNODECONDITION", condition, "left") 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_persistentvolumeclaim.rb: -------------------------------------------------------------------------------- 1 | class K8S_PERSISTENTVOLUMECLAIM < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | k8s_resource_upsert(@asset, @asset_type, @asset_label, @asset_name) 10 | 11 | cluster = k8s_cluster_name(@asset_name) 12 | spec_sc_name = @asset.dig('resource', 'data', 'spec', 'storageClassName') || 'standard' 13 | sc_name = "#{cluster}/apis/storage.k8s.io/v1/storageclasses/#{spec_sc_name}" 14 | unless spec_sc_name.nil? && sc_name.nil? 15 | supporting_relationship(@asset_label, @asset_name, "K8S_STORAGECLASS", sc_name, "k8s.io/StorageClass", "k8s", "HAS_K8SSTORAGECLASS", "left") 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_service.rb: -------------------------------------------------------------------------------- 1 | class K8S_SERVICE < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | service_ports = [] 10 | if asset.dig('resource','data','spec','ports') 11 | service_ports = asset.dig('resource', 'data', 'spec').delete('ports') 12 | end 13 | 14 | k8s_resource_upsert(@asset, @asset_type, @asset_label, @asset_name) 15 | 16 | unless service_ports.nil? 17 | service_ports.each do |sp| 18 | sp_name = sp['name'] || "#{sp['protocol']}-#{sp['port']}" 19 | sp.delete('name') 20 | supporting_relationship_with_attrs("K8S_SERVICE", @asset_name, "K8S_SERVICEPORT", sp_name, "k8s.io/ServicePort", sp, "k8s", "HAS_K8SSERVICEPORT", {}, "left") 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_serviceaccount.rb: -------------------------------------------------------------------------------- 1 | class K8S_SERVICEACCOUNT < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | 9 | secret_names = [] 10 | if asset.dig('resource','data','secrets') 11 | secret_names = asset.dig('resource', 'data').delete('secrets') 12 | end 13 | k8s_resource_upsert(@asset, @asset_type, @asset_label, @asset_name) 14 | 15 | unless secret_names.nil? 16 | secret_names.each do |secret| 17 | secret_name = secret['name'] || "Unnamed" 18 | secret.delete('name') 19 | if matches = @asset_name.match(%r{(?.*\/)serviceaccounts\/(?.*)}) 20 | full_secret_name = "#{matches[:base]}secrets/#{secret_name}" 21 | supporting_relationship_with_attrs("K8S_SERVICEACCOUNT", @asset_name, "K8S_SECRET", full_secret_name, "k8s.io/Secret", {}, "k8s", "HAS_K8SSECRET", {}, "left") 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/loaders/k8s_loader/k8s_version.rb: -------------------------------------------------------------------------------- 1 | class K8S_VERSION < K8sLoader 2 | 3 | def initialize(asset, db, import_id) 4 | super 5 | end 6 | 7 | def load 8 | k8s_resource_upsert(@asset, @asset_type, @asset_label, @asset_name) 9 | 10 | # Maps the K8S_CLUSTER to the corresponding GKE or EKS Cluster Resource 11 | # TODO: EKS Relation 12 | cluster = k8s_cluster_name(@asset_name) 13 | if @asset_name.match(/^container.googleapis.com/) 14 | # GKE 15 | supporting_relationship("K8S_CLUSTER", cluster, "GCP_CONTAINER_CLUSTER", cluster, "container.googleapis.com/Cluster", "k8s", "HAS_K8SCLUSTER", "right") 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/asset/utils/enumerable.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | # Custom patch to flatten keys and sanitize values 4 | module Enumerable 5 | def flatten_with_path(parent_prefix = nil) 6 | res = {} 7 | 8 | self.each_with_index do |elem, i| 9 | if elem.is_a?(Array) 10 | k, v = elem 11 | else 12 | k, v = i, elem 13 | end 14 | 15 | key = parent_prefix ? "#{parent_prefix}_#{k}" : k # assign key name for result hash 16 | key = key.gsub(/-/,'_').gsub(/\./, '_dot_').gsub(/\//, '_slash_') 17 | 18 | if v.is_a? Enumerable 19 | res.merge!(v.flatten_with_path(key)) # recursive call to flatten child elements 20 | else 21 | if v.is_a?(String) 22 | res[key] = sanitize_value(v) 23 | elsif v.nil? 24 | res[key] = 'null' 25 | else 26 | res[key] = v 27 | end 28 | end 29 | end 30 | 31 | res 32 | end 33 | 34 | def sanitize_value(value) 35 | value = CGI.escape(value) if value.start_with?('{') 36 | value&.gsub(%r{/^//}, '') 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/loader/file_loader.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'loader/asset/asset_router' 4 | require 'redisgraph' 5 | require 'db/redisgraph' 6 | require 'loader/misc/post_loader' 7 | require 'fast_jsonparser' 8 | require 'parallel' 9 | 10 | # Given an array of files, for each file, for each 11 | # line, validates the line as jsonlines, determines 12 | # asset type, and dispatches to the proper loader 13 | class FileLoader 14 | include GraphDB::DB 15 | 16 | attr_accessor :db_config, :files_to_load 17 | 18 | def initialize(db_config, files_to_load) 19 | @import_id = Time.now.utc.to_i 20 | @db_config = db_config 21 | @db = db_connection(db_config) 22 | @files_to_load = files_to_load 23 | load 24 | end 25 | 26 | def load 27 | loop_over_files(@files_to_load) 28 | PostLoader.new(@db, @import_id) 29 | end 30 | 31 | private 32 | 33 | def loop_over_files(files) 34 | files.each do |file_name| 35 | loop_over_asset_lines(file_name) 36 | end 37 | end 38 | 39 | # Batch load file, send each line to asset router 40 | def loop_over_asset_lines(file_name) 41 | line_count = 0 42 | puts "Loading #{file_name}" 43 | batch_size = 25000 44 | File.open(file_name) do |file| 45 | file.each_slice(batch_size) do |lines| 46 | line_count += lines.length 47 | Parallel.each(lines, in_processes: 8) do |line| 48 | begin 49 | asset_json = FastJsonparser.parse(line, symbolize_keys: false) 50 | AssetRouter.new(asset_json, @import_id, @db) 51 | line_count += 1 52 | rescue FastJsonparser::ParseError => e 53 | puts "[file_loader] Error: JSON can't be parsed. Ensure you are loading NDJSON files." 54 | raise e 55 | end 56 | end 57 | end 58 | end 59 | 60 | puts "\n\nDone loading #{file_name}. (#{line_count} lines)" 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/validator/cai.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'json' 4 | require 'json_schemer' 5 | 6 | # Validation module 7 | module Validator 8 | # Validates a CAI file format 9 | module CAI 10 | def validate_cai(path) 11 | IO.foreach(path) do |line| 12 | validate_schema(parse_json_line(line)) 13 | end 14 | end 15 | 16 | def validate_schema(json) 17 | cai_schema = Pathname.new('app/jobs/lib/validator/schema/cai.json') 18 | JSONSchemer.schema(cai_schema).valid?(json) 19 | end 20 | 21 | def parse_json_line(line) 22 | JSON.parse(line) 23 | rescue JSON::ParserError => e 24 | raise "JSON Parser exception #{e}" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docker/app/jobs/lib/validator/schema/cai.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": {}, 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "http://example.com/root.json", 5 | "type": "object", 6 | "title": "The Root Schema", 7 | "required": [ 8 | "name", 9 | "asset_type" 10 | ], 11 | "properties": { 12 | "name": { 13 | "$id": "#/properties/name", 14 | "type": "string", 15 | "title": "The Name Schema", 16 | "default": "", 17 | "examples": [ 18 | "//cloudresourcemanager.googleapis.com/organizations/684587186245" 19 | ], 20 | "pattern": "^(.*)$" 21 | }, 22 | "asset_type": { 23 | "$id": "#/properties/asset_type", 24 | "type": "string", 25 | "title": "The Asset_type Schema", 26 | "default": "", 27 | "examples": [ 28 | "cloudresourcemanager.googleapis.com/Organization" 29 | ], 30 | "pattern": "^(.*)$" 31 | }, 32 | "resource": { 33 | "$id": "#/properties/resource", 34 | "type": "object", 35 | "title": "The Resource Schema", 36 | "required": [ 37 | "data" 38 | ] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docker/app/jobs/loader_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add lib to the load path 4 | $:.unshift(File.expand_path('lib', __dir__)) 5 | require 'batch_importer' 6 | require 'pry' 7 | 8 | class LoaderJob < ApplicationJob 9 | queue_as :default 10 | CONFIG_FILE = 'load_config/config.yaml' 11 | 12 | TYPE = :load 13 | 14 | def perform(args) 15 | unless args.key?(:guid) 16 | logger.info 'Loader job not running without a GUID.' 17 | return nil 18 | end 19 | 20 | # shared GUID 21 | guid = args[:guid] 22 | logger.info "#{guid} Loader job started" 23 | 24 | # Track the job 25 | job = Job.create(status: :running, kind: TYPE, guid: guid) 26 | 27 | logger.info 'Loading data' 28 | 29 | begin 30 | BatchImporter.new(CONFIG_FILE).import 31 | job.complete! 32 | puts "Loader job finished - #{guid}" 33 | rescue StandardError => e 34 | job.failed! 35 | puts "Loader job failed - #{e.class} #{e.message} (#{e.backtrace[0]})" 36 | 37 | # re-raise for RunnerJob 38 | raise e 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /docker/app/jobs/spec/formatters/custom_formatter.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'yaml' 3 | require 'json' 4 | 5 | class CspmFormatter #< RSpec::Core::Formatters::BaseFormatter 6 | RSpec::Core::Formatters.register self, :close, :dump_summary, :start 7 | 8 | attr_accessor :findings 9 | 10 | def initialize(output) 11 | @output = output 12 | end 13 | 14 | def start(notification) 15 | end 16 | 17 | def dump_summary(notification) 18 | findings = [] 19 | notification.examples.each do |example| 20 | finding = Hash.new 21 | finding['control_pack'] = example.metadata[:example_group][:control_pack] || File.basename(File.dirname(example.metadata[:file_path])) 22 | finding['control_id'] = example.metadata[:example_group][:control_id] 23 | finding['resource'] = example.metadata[:example_group][:description] 24 | finding['status'] = example.metadata[:execution_result].status.to_s 25 | findings << finding 26 | end 27 | results = [] 28 | findings.group_by{ |r| { "control_pack" => r['control_pack'], "control_id" => r['control_id']} }.each do |c,res| 29 | c['resources'] = res.map { |r| { "name" => r['resource'], "status" => r['status'] } } 30 | results << c 31 | end 32 | File.open('/tmp/results', 'w') { |file| file.write(results.to_json) } 33 | end 34 | 35 | def close(notification) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /docker/app/jobs/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'active_support/time' 3 | require 'rspec/core/formatters/base_formatter' 4 | require_relative 'formatters/custom_formatter' 5 | require_relative 'support/db_helper' 6 | require_relative 'support/redisgraph_helper' 7 | require 'json' 8 | -------------------------------------------------------------------------------- /docker/app/jobs/spec/support/db_helper.rb: -------------------------------------------------------------------------------- 1 | require 'redisgraph' 2 | 3 | def graphdb 4 | account_name = 'opencspm' 5 | db_config = { url: 'redis://redis:6379' } 6 | db ||= RedisGraph.new(account_name, db_config) 7 | end 8 | -------------------------------------------------------------------------------- /docker/app/jobs/spec/support/redisgraph_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Add a custom method RedisGraph QueryResult to return 3 | # an array of Structs instead of an array of Arrays. 4 | # 5 | # Original: https://github.com/RedisGraph/redisgraph-rb/blob/master/lib/redisgraph/query_result.rb 6 | # 7 | class QueryResult 8 | def mapped_results 9 | if columns && columns.length > 0 10 | resultset.map do |result| 11 | hash = {} 12 | 13 | columns.each_with_index do |col, idx| 14 | hash[col] = result[idx] 15 | end 16 | 17 | OpenStruct.new(hash) 18 | end 19 | else 20 | resultset 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /docker/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /docker/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /docker/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/app/models/concerns/.keep -------------------------------------------------------------------------------- /docker/app/models/control.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Control < ApplicationRecord 4 | has_many :taggings, dependent: :destroy 5 | has_many :tags, through: :taggings 6 | has_many :results 7 | has_many :jobs, through: :results 8 | has_many :issues, through: :results 9 | has_many :resources, -> { distinct }, through: :results 10 | 11 | # create a custom json hash with mapped tags including primary status 12 | scope :with_mapped_tags, lambda { 13 | joins(taggings: :tag) 14 | .select('controls.*') 15 | .select('json_agg(json_build_object(\'tag\', tags.name, \'primary\', taggings.primary)) AS tag_map') 16 | .group('controls.id') 17 | } 18 | 19 | enum status: { failed: -1, unknown: 0, passed: 1 } 20 | 21 | # 22 | # Tags 23 | # 24 | def tag_list 25 | tags.join(', ') 26 | end 27 | 28 | # 29 | # Tags 30 | # 31 | def tag_list=(tags_string) 32 | tag_names = tags_string.split(',').collect { |s| s.strip.downcase }.uniq 33 | new_or_found_tags = tag_names.collect { |name| Tag.find_or_create_by(name: name) } 34 | self.tags = new_or_found_tags 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /docker/app/models/identity.rb: -------------------------------------------------------------------------------- 1 | class Identity < ApplicationRecord 2 | belongs_to :user 3 | validates_presence_of :uid, :provider 4 | validates_uniqueness_of :uid, scope: :provider 5 | 6 | def self.find_for_oauth(auth) 7 | find_or_create_by(uid: auth.uid, provider: auth.provider) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/models/issue.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Issue < ApplicationRecord 4 | belongs_to :result 5 | belongs_to :resource 6 | 7 | enum status: { passed: 0, failed: 1 } 8 | end 9 | -------------------------------------------------------------------------------- /docker/app/models/job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Generic Job 5 | # 6 | class Job < ApplicationRecord 7 | has_many :results 8 | 9 | enum status: { 10 | running: 0, 11 | complete: 1, 12 | failed: -1 13 | } 14 | enum kind: { 15 | run: 0, 16 | load: 1, 17 | analyze: 2, 18 | parse: 3, 19 | cleanup: 4 20 | }, _prefix: :job 21 | 22 | # 23 | # Send an external webhook 24 | # 25 | def send_webhook 26 | config = Rails.application.config_for(:webhooks) 27 | 28 | return unless config.webhooks 29 | 30 | config.webhooks.each do |_k, webhook| 31 | next if webhook.nil? 32 | 33 | url = URI(webhook) 34 | headers = { 'content-type' => 'application/json' } 35 | payload = { 36 | 'job_id' => id, 37 | 'job_type' => kind, 38 | 'status' => status, 39 | 'results' => results.count 40 | }.to_json 41 | res = Net::HTTP.post(URI(url), payload, headers) 42 | 43 | res.code 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /docker/app/models/organization.rb: -------------------------------------------------------------------------------- 1 | class Organization < ApplicationRecord 2 | has_many :users, dependent: :destroy 3 | has_many :campaigns 4 | end 5 | -------------------------------------------------------------------------------- /docker/app/models/profile.rb: -------------------------------------------------------------------------------- 1 | class Profile < ApplicationRecord 2 | has_many :jobs 3 | 4 | # 5 | # TODO: Read from local profile .yml files instead of db/seeds.rb 6 | # 7 | def sync_from_files 8 | # File read YAML load 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /docker/app/models/resource.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Resource < ApplicationRecord 4 | has_many :issues 5 | has_many :results, through: :issues 6 | 7 | private 8 | 9 | # 10 | # TODO: write this 11 | # 12 | def cleanup 13 | # destroy resources with no issues 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docker/app/models/result.rb: -------------------------------------------------------------------------------- 1 | class Result < ApplicationRecord 2 | belongs_to :job 3 | belongs_to :control 4 | has_many :issues, dependent: :destroy 5 | has_many :resources, through: :issues 6 | end 7 | -------------------------------------------------------------------------------- /docker/app/models/role.rb: -------------------------------------------------------------------------------- 1 | class Role < ApplicationRecord 2 | has_and_belongs_to_many :users, join_table: :users_roles 3 | 4 | belongs_to :resource, 5 | polymorphic: true, 6 | optional: true 7 | 8 | validates :resource_type, 9 | inclusion: { in: Rolify.resource_types }, 10 | allow_nil: true 11 | 12 | scopify 13 | end 14 | -------------------------------------------------------------------------------- /docker/app/models/source.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Source < ApplicationRecord 4 | enum status: { 5 | disabled: 0, 6 | active: 1, 7 | scan_requested: 2, 8 | scanning: 3 9 | } 10 | 11 | def schedule_worker 12 | RunnerJob.perform_later if scan_requested? 13 | end 14 | 15 | def refresh 16 | # read config file, find or create a source for each source listed 17 | config_sources = JSON.parse(YAML.load_file(LoaderJob::CONFIG_FILE).to_json, object_class: OpenStruct) 18 | 19 | return unless config_sources&.local_dirs&.length&.positive? 20 | 21 | config_sources.local_dirs.each do |dir| 22 | puts "Config file local dir source=#{dir}" 23 | source = Source.find_or_create_by(name: dir) 24 | source.active! 25 | end 26 | 27 | # loop through all existing sources, disable if not in config file 28 | Source.all.each do |source| 29 | puts "Config file system source=#{source.name} status=#{source.status}" 30 | source.disabled! unless config_sources.local_dirs.include?(source.name) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /docker/app/models/tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Tag < ApplicationRecord 4 | has_many :taggings 5 | has_many :controls, through: :taggings 6 | 7 | def to_s 8 | name 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /docker/app/models/tagging.rb: -------------------------------------------------------------------------------- 1 | class Tagging < ApplicationRecord 2 | belongs_to :tag 3 | belongs_to :control 4 | 5 | scope :primary, -> { where(primary: true) } 6 | end 7 | -------------------------------------------------------------------------------- /docker/app/serializers/campaign_controls_serializer.rb: -------------------------------------------------------------------------------- 1 | class CampaignControlsSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :id, 4 | :guid, 5 | :control_id, 6 | :title, 7 | :tag_map, 8 | :impact, 9 | :status, 10 | :resources_failed, 11 | :resources_total 12 | end 13 | -------------------------------------------------------------------------------- /docker/app/serializers/campaign_serializer.rb: -------------------------------------------------------------------------------- 1 | class CampaignSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :name, 4 | :filters 5 | 6 | attribute :controls do |campaign| 7 | CampaignControlsSerializer.new(campaign.controls) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/serializers/campaigns_serializer.rb: -------------------------------------------------------------------------------- 1 | class CampaignsSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :id, :name, :filters, :control_count, :updated_at 4 | end 5 | -------------------------------------------------------------------------------- /docker/app/serializers/control_serializer.rb: -------------------------------------------------------------------------------- 1 | class ControlSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :id, 4 | :guid, 5 | :control_pack, 6 | :control_id, 7 | :title, 8 | :description, 9 | :remediation, 10 | :validation, 11 | :refs, 12 | :impact, 13 | :status, 14 | :resources 15 | 16 | attribute :resources do |control| 17 | if control.results.empty? 18 | [] 19 | else 20 | control.results.last.issues.includes(:resource).map { |issue| { status: issue.status, name: issue.resource.name } } 21 | end 22 | end 23 | 24 | attribute :tags do |control| 25 | control.taggings.includes(:tag).map { |t| { tag: t.tag.name, primary: t.primary } } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docker/app/serializers/controls_serializer.rb: -------------------------------------------------------------------------------- 1 | class ControlsSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :id, 4 | :guid, 5 | :control_id, 6 | :title, 7 | :tag_map, 8 | :impact, 9 | :status, 10 | :resources_failed, 11 | :resources_total 12 | end 13 | -------------------------------------------------------------------------------- /docker/app/serializers/profile_serializer.rb: -------------------------------------------------------------------------------- 1 | class ProfileSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :name, :status, :platform, :tag, :issue_count 4 | 5 | # TODO: move to join in ProfilesController 6 | attribute :controls do |profile| 7 | profile&.controls 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/serializers/profiles_serializer.rb: -------------------------------------------------------------------------------- 1 | class ProfilesSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :id, :name, :author, :platform, :tags 4 | 5 | attribute :controls do |profile| 6 | # TODO: optionally include ALL tags, not just ANY tag 7 | Control.includes(:tags).where(tags: { name: profile.tags }) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker/app/serializers/results_serializer.rb: -------------------------------------------------------------------------------- 1 | class ResultsSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :id, :job_id, :data, :observed_at 4 | end 5 | -------------------------------------------------------------------------------- /docker/app/serializers/tag_serializer.rb: -------------------------------------------------------------------------------- 1 | class TagSerializer 2 | include FastJsonapi::ObjectSerializer 3 | attributes :name 4 | end 5 | -------------------------------------------------------------------------------- /docker/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /docker/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /docker/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /docker/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /docker/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to setup or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?('config/database.yml') 22 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! 'bin/rails db:prepare' 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! 'bin/rails log:clear tmp:clear' 30 | 31 | puts "\n== Restarting application server ==" 32 | system! 'bin/rails restart' 33 | end 34 | -------------------------------------------------------------------------------- /docker/bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads Spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docker/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /docker/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails' 4 | # Pick the frameworks you want: 5 | require 'active_model/railtie' 6 | require 'active_job/railtie' 7 | require 'active_record/railtie' 8 | # require "active_storage/engine" 9 | require 'action_controller/railtie' 10 | require 'action_mailer/railtie' 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require 'action_view/railtie' 14 | require 'action_cable/engine' 15 | # require "sprockets/railtie" 16 | require 'rails/test_unit/railtie' 17 | 18 | # Require the gems listed in Gemfile, including any gems 19 | # you've limited to :test, :development, or :production. 20 | Bundler.require(*Rails.groups) 21 | 22 | module Opencspm 23 | class Application < Rails::Application 24 | # Initialize configuration defaults for originally generated Rails version. 25 | config.load_defaults 6.0 26 | 27 | # JL 28 | # config.autoload_paths << Rails.root.join('lib') 29 | config.eager_load_paths << Rails.root.join('lib') 30 | 31 | # Settings in config/environments/* take precedence over those specified here. 32 | # Application configuration can go into files in config/initializers 33 | # -- all .rb files in that directory are automatically loaded after loading 34 | # the framework and any gems in your application. 35 | 36 | # Only loads a smaller set of middleware suitable for API only apps. 37 | # Middleware like session, flash, cookies can be added back manually. 38 | # Skip views, helpers and assets when generating a new resource. 39 | config.api_only = true 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /docker/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /docker/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: opencspm_production 11 | -------------------------------------------------------------------------------- /docker/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | 5us0FKDipLvp+aUyT12b+2Z6SWCdkcgJKOaDfDpq6CGka3ZVFsWkq4PrzBEc+A/5iAe5VG0H4BGq7jlLHluPaj6lAcV8xz2KF9wOTp8FZu22gMTAgP/o6aT95cgeZWfKH8fpCklPgstLa/3oOaI8uy+UEQ/IlishA5k7kkpmyjHzfV38FHm0tqPSlFPsOniOgAxUllKKQhGmH6ae4Z/tNUsHHbsJ27QnefpRJKuiLyuon31bNNFson3fkUfJnjgdrsjV1T4MrRY+mL2iN6USdkSPZOhC76icc4oQfg1cQnS+4NGr0drbB2gzmiz1wTzlukfN+KVJbyjAIQ==--hMsH7vL7U4Pkrkbj--fkjrhvFzXsVZiL2LLbMWfA== -------------------------------------------------------------------------------- /docker/config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.3 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On macOS with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On macOS with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see Rails configuration guide 21 | # https://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | url: <%= ENV['DATABASE_URL'] %> 24 | 25 | development: 26 | <<: *default 27 | database: opencspm_development 28 | 29 | test: 30 | <<: *default 31 | database: opencspm_test 32 | 33 | production: 34 | <<: *default 35 | database: opencspm_production 36 | -------------------------------------------------------------------------------- /docker/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /docker/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 18 | config.cache_store = :memory_store 19 | config.public_file_server.headers = { 20 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 21 | } 22 | else 23 | config.action_controller.perform_caching = false 24 | 25 | config.cache_store = :null_store 26 | end 27 | 28 | # Don't care if the mailer can't send. 29 | config.action_mailer.raise_delivery_errors = true 30 | 31 | # Devise 32 | config.action_mailer.default_url_options = { host: 'localhost', port: 5000 } 33 | 34 | config.action_mailer.perform_caching = false 35 | 36 | # Print deprecation notices to the Rails logger. 37 | config.active_support.deprecation = :log 38 | 39 | # Raise an error on page load if there are pending migrations. 40 | config.active_record.migration_error = :page_load 41 | 42 | # Highlight code that triggered database queries in logs. 43 | config.active_record.verbose_query_logs = true 44 | 45 | # Raises error for missing translations. 46 | # config.action_view.raise_on_missing_translations = true 47 | 48 | # Use an evented file watcher to asynchronously detect changes in source code, 49 | # routes, locales, etc. This feature depends on the listen gem. 50 | 51 | ## Disable for Docker 52 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker 53 | # config.file_watcher = ActiveSupport::FileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /docker/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | Rails.application.configure do 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | config.cache_classes = false 10 | config.action_view.cache_template_loading = true 11 | 12 | # Do not eager load code on boot. This avoids loading your whole application 13 | # just for the purpose of running a single test. If you are using a tool that 14 | # preloads Rails for running tests, you may have to set it to true. 15 | config.eager_load = false 16 | 17 | # Configure public file server for tests with Cache-Control for performance. 18 | config.public_file_server.enabled = true 19 | config.public_file_server.headers = { 20 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 21 | } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | config.cache_store = :null_store 27 | 28 | # Raise exceptions instead of rendering exception templates. 29 | config.action_dispatch.show_exceptions = false 30 | 31 | # Disable request forgery protection in test environment. 32 | config.action_controller.allow_forgery_protection = false 33 | 34 | config.action_mailer.perform_caching = false 35 | 36 | # Tell Action Mailer not to deliver emails to the real world. 37 | # The :test delivery method accumulates sent emails in the 38 | # ActionMailer::Base.deliveries array. 39 | config.action_mailer.delivery_method = :test 40 | 41 | # Print deprecation notices to the stderr. 42 | config.active_support.deprecation = :stderr 43 | 44 | # Raises error for missing translations. 45 | # config.action_view.raise_on_missing_translations = true 46 | end 47 | -------------------------------------------------------------------------------- /docker/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /docker/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /docker/config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | allow do 10 | origins Rails.application.config.ui_host 11 | 12 | resource '*', 13 | headers: :any, 14 | credentials: true, 15 | methods: %i[get post put patch delete options head] 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docker/config/initializers/demo.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load demo data 3 | # 4 | 5 | # DON'T USE THIS 6 | -------------------------------------------------------------------------------- /docker/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += %i[password password_confirmation encrypted_password] 5 | -------------------------------------------------------------------------------- /docker/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /docker/config/initializers/lograge.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.lograge.enabled = true 3 | config.lograge.base_controller_class = 'ActionController::API' 4 | config.lograge.ignore_actions = ['HealthcheckController#index'] 5 | 6 | # For debugging, re-enable original Rails verbose style logging to file 7 | # 8 | # config.lograge.keep_original_rails_log = true 9 | # config.lograge.logger = ActiveSupport::Logger.new "#{Rails.root}/log/#{Rails.env}.log" 10 | end 11 | -------------------------------------------------------------------------------- /docker/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /docker/config/initializers/omniauth.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.middleware.use OmniAuth::Builder do 2 | # creds = RecursiveOpenStruct.new(Rails.application.credentials.config).oauth[Rails.env] 3 | 4 | # provider :google_oauth2, creds.google.client_id, creds.google.client_secret, { name: 'google', scope: 'userinfo.email' } 5 | # provider :github, creds.github.client_id, creds.github.client_secret, scope: 'user:email' 6 | 7 | # ensure callback is always https 8 | # OmniAuth.config.full_host = Rails.env.production? ? 'https://auth.opencspm.org' : 'http://localhost:5000' 9 | # OmniAuth.config.allowed_request_methods = [:post] 10 | end 11 | -------------------------------------------------------------------------------- /docker/config/initializers/rolify.rb: -------------------------------------------------------------------------------- 1 | Rolify.configure do |config| 2 | # Dynamic shortcuts for User class (user.is_admin? like methods). Default is: false 3 | config.use_dynamic_shortcuts 4 | 5 | # Configuration to remove roles from database once the last resource is removed. Default is: true 6 | config.remove_role_if_empty = false 7 | end 8 | 9 | # TODO: remove 10 | Rails.application.configure do 11 | # For testing 12 | config.authorized_for_admin_role = ['josh@darkbit.io', 'brad@darkbit.io'] 13 | end 14 | -------------------------------------------------------------------------------- /docker/config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # force log level down to :info 4 | # Sidekiq::Logging.logger.level = Logger::INFO if Rails.env.development? 5 | 6 | # https://github.com/ondrejbartas/sidekiq-cron/issues/249#issuecomment-577492567 7 | # sidekiq-cron 8 | if Sidekiq.server? 9 | Rails.application.config.after_initialize do 10 | h = YAML.load_file(Rails.root.join('config', 'scheduled-jobs.yml')) 11 | Sidekiq::Cron::Job.load_from_hash(h) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /docker/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /docker/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /docker/config/locales/errors.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | error: 3 | authentication_failure: 4 | http_code: 401 5 | code: 401 6 | message: 'The user is not authenticated.' 7 | database_connect_timeout: 8 | code: 5001 9 | message: 'The database connection timed out.' 10 | graph_loader_params_missing: 11 | message: 'Missing parameters' 12 | -------------------------------------------------------------------------------- /docker/config/master.key: -------------------------------------------------------------------------------- 1 | 3d038aef3f40475877d97a2c330f32e4 -------------------------------------------------------------------------------- /docker/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 12 | # 13 | port ENV.fetch("PORT") { 3000 } 14 | 15 | # Specifies the `environment` that Puma will run in. 16 | # 17 | environment ENV.fetch("RAILS_ENV") { "development" } 18 | 19 | # Specifies the `pidfile` that Puma will use. 20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 21 | 22 | # Specifies the number of `workers` to boot in clustered mode. 23 | # Workers are forked web server processes. If using threads and workers together 24 | # the concurrency of the application would be max `threads` * `workers`. 25 | # Workers do not work on JRuby or Windows (both of which do not support 26 | # processes). 27 | # 28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 29 | 30 | # Use the `preload_app!` method when specifying a `workers` number. 31 | # This directive tells Puma to first boot the application and load code 32 | # before forking the application. This takes advantage of Copy On Write 33 | # process behavior so workers use less memory. 34 | # 35 | # preload_app! 36 | 37 | # Allow puma to be restarted by `rails restart` command. 38 | plugin :tmp_restart 39 | -------------------------------------------------------------------------------- /docker/config/routes.rb: -------------------------------------------------------------------------------- 1 | require 'sidekiq/web' 2 | require 'sidekiq/cron/web' 3 | 4 | Rails.application.routes.draw do 5 | mount Sidekiq::Web => '/sidekiq' 6 | 7 | # --------------- 8 | # Health check and smoke test routes 9 | # --------------- 10 | get 'healthz', to: 'healthcheck#index', as: 'health_check' 11 | get 'statusz', to: 'healthcheck#show', as: 'status_check' 12 | 13 | # --------------- 14 | # Auth routes 15 | # --------------- 16 | get 'auth/:provider/callback', to: 'sessions#create' 17 | get 'auth/:provider', to: 'sessions#new' 18 | get 'auth/failure', to: redirect('/') # TODO: fix this to redirect to the web UI 19 | 20 | # --------------- 21 | # Resource routes 22 | # --------------- 23 | namespace :api do 24 | resources :sessions, only: %i[create index show destroy] 25 | resources :organizations 26 | resources :campaigns do 27 | resources :results, only: %i[index], to: 'campaign_results#index' 28 | end 29 | resources :profiles, only: %i[index show] 30 | resources :controls, only: %i[index show] 31 | resources :sources, only: %i[index show update] 32 | resources :jobs, only: %i[index] 33 | resources :users 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /docker/config/scheduled-jobs.yml: -------------------------------------------------------------------------------- 1 | result_loader_job: 2 | name: 'Start runner job' 3 | cron: '5 * * * *' 4 | class: RunnerJob 5 | queue: default 6 | description: Runs runner job 7 | -------------------------------------------------------------------------------- /docker/config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 2 3 | :queues: 4 | - default 5 | -------------------------------------------------------------------------------- /docker/config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /docker/config/webhooks.yml: -------------------------------------------------------------------------------- 1 | development: 2 | webhooks: 3 | runner_job: <%= ENV['JOB_WEBHOOK_URL'] %> 4 | production: 5 | webhooks: 6 | runner_job: <%= ENV['JOB_WEBHOOK_URL'] %> 7 | -------------------------------------------------------------------------------- /docker/controls/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/controls/.keep -------------------------------------------------------------------------------- /docker/db/sample_profiles.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: All AWS 3 | platform: AWS 4 | author: Darkbit 5 | tags: 6 | - aws 7 | - name: All Google Cloud 8 | platform: Google Cloud 9 | author: Darkbit 10 | tags: 11 | - google cloud 12 | - name: All Kubernetes 13 | platform: Kubernetes 14 | author: Darkbit 15 | tags: 16 | - kubernetes 17 | - name: AWS Well Architected (Security) 18 | platform: AWS 19 | author: Darkbit 20 | tags: 21 | - aws-wa-security 22 | - name: AWS Config/Conformance Packs 23 | platform: AWS 24 | author: Darkbit 25 | tags: 26 | - aws-cfg 27 | - name: NIST Cybersecurity Framework (CSF) 28 | platform: Any 29 | author: Darkbit 30 | tags: 31 | - nist-csf 32 | - name: NIST 800-53 rev4 33 | platform: Any 34 | author: Darkbit 35 | tags: 36 | - nist-800-53-rev4 37 | - name: NIST 800-171 38 | platform: Any 39 | author: Darkbit 40 | tags: 41 | - nist-800-171 42 | - name: PCI DSS 3.2.1 43 | platform: Any 44 | author: Darkbit 45 | tags: 46 | - pci-dss-3.2.1 47 | - name: HIPAA 48 | platform: Any 49 | author: Darkbit 50 | tags: 51 | - hipaa 52 | - name: CIS Benchmarks 53 | platform: Any 54 | author: Darkbit 55 | tags: 56 | - cis 57 | - name: FedRAMP 58 | platform: Any 59 | author: Darkbit 60 | tags: 61 | - fedramp-moderate 62 | - fedramp-low 63 | - name: Cybersecurity Maturity Model Certification (CMMC) 64 | platform: Any 65 | author: Darkbit 66 | tags: 67 | - cmmc-level1 68 | - cmmc-level2 69 | - cmmc-level3 70 | - cmmc-level4 71 | - cmmc-level5 72 | -------------------------------------------------------------------------------- /docker/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | 9 | # Demo Org 10 | org = Organization.find_or_create_by(name: 'Demo Organization') 11 | 12 | # Demo User 13 | demo_user = User.find_by(username: 'demo') 14 | 15 | unless demo_user 16 | demo_user = User.new( 17 | name: 'Demo', 18 | username: 'demo', 19 | password_digest: BCrypt::Password.create('demo'), 20 | organization: org 21 | ) 22 | 23 | demo_user.save(validate: false) 24 | demo_user.add_role(:admin) 25 | end 26 | 27 | # Data sources (Loaders) 28 | sources = JSON.parse(YAML.load(File.read('load_config/config.yaml')).to_json) 29 | 30 | sources['buckets']&.each do |ds| 31 | res = Source.find_or_create_by(name: ds) 32 | puts "Source: #{ds}" 33 | end 34 | 35 | sources['local_dirs']&.each do |ds| 36 | res = Source.find_or_create_by(name: ds) 37 | puts "Source: #{ds}" 38 | end 39 | 40 | # Profiles 41 | profiles = YAML.load(File.read('db/sample_profiles.yml')) 42 | 43 | profiles.each do |p| 44 | res = Profile.find_or_create_by(name: p['name']) 45 | # res.tags = p['tags'].map { |t| t.downcase } 46 | res.update(p) 47 | end 48 | 49 | # Import Controls from Control Packs 50 | PackJob.perform_later 51 | -------------------------------------------------------------------------------- /docker/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # In development, run `bundle install` once and run debugger. 4 | # 5 | # In prod, cleanup pids and run server. 6 | # 7 | 8 | FILE=".init" 9 | INSTALL="bundle install" 10 | SETUP="bundle exec rails db:setup" 11 | CLEANUP="rm -f /app/tmp/pids/server.pid" 12 | RUN="bundle exec rails server -p 5000 -b 0.0.0.0" 13 | DEBUG="bundle exec rdebug-ide --debug --host 0.0.0.0 --port 4444 -- bin/rails server -p 5000 -b 0.0.0.0" 14 | 15 | if [ "$DEBUGGER" == "true" ]; then 16 | echo "[bundle] Setting up debugger..." 17 | RUN=${DEBUG} 18 | fi 19 | 20 | if [[ -f ${FILE} ]]; then 21 | ${CLEANUP} 22 | ${RUN} 23 | else 24 | 25 | # bundle install in development 26 | if [ "$RAILS_ENV" == "development" ]; then 27 | echo "[bundle] First run detected, installing..." 28 | ${INSTALL} 29 | fi 30 | 31 | # 32 | touch ${FILE} && ${SETUP} 33 | 34 | ${RUN} 35 | fi 36 | -------------------------------------------------------------------------------- /docker/lib/application_error.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Generic error class - http://abhisheksarka.github.io/custom-errors-in-rails/ 3 | # 4 | class ApplicationError < StandardError 5 | # responds to all the below attributes 6 | attr_accessor :config, 7 | :code, 8 | :message, 9 | :http_code 10 | 11 | # We pass config hash of the sort 12 | # {code: 100, message: 'Some Error Message', http_code: 501} 13 | def initialize(config) 14 | @config = config 15 | @code = config[:code] 16 | @message = config[:message] 17 | @http_code = config[:http_code] 18 | end 19 | 20 | class << self 21 | # Whenever we call something like ApplicationError::SomeCustomError 22 | # this method is triggered. We then do a look up in an error configuration 23 | # file, discussed below, and then determine what error object to instantiate 24 | def const_missing(name) 25 | I18n.reload! 26 | err_hash = t(name) 27 | if err_hash.is_a? Hash 28 | err_hash[:name] = name 29 | ApplicationError.new(err_hash) 30 | else 31 | super 32 | end 33 | end 34 | 35 | def t(error_name) 36 | I18n.t("error.#{error_name.to_s.underscore}") 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /docker/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/lib/tasks/.keep -------------------------------------------------------------------------------- /docker/lib/tasks/batch_import.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $:.unshift('/app/app/jobs/lib') 4 | require 'batch_importer' 5 | require 'pry' 6 | 7 | namespace :batch do 8 | desc 'Loads All Data from Import' 9 | task import: :environment do 10 | BatchImporter.new(LoaderJob::CONFIG_FILE).import 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /docker/lib/tasks/control_pack_links.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :packs do 4 | desc 'Checks URLs used in Control Packs' 5 | task check_links: :environment do 6 | check_links 7 | end 8 | 9 | def check_links 10 | puts 'Checking links...' 11 | 12 | controls = [] 13 | urls = [] 14 | broken = [] 15 | 16 | # load control packs 17 | Dir[PACK_BASE_DIR].each do |file| 18 | next if file.starts_with?('controls/_') 19 | 20 | puts file 21 | 22 | controls.push( 23 | JSON.parse( 24 | YAML.safe_load(File.read(file)).to_json, object_class: OpenStruct 25 | ).controls 26 | ) 27 | end 28 | 29 | # flatten 30 | controls = controls.flatten 31 | 32 | controls.each do |control| 33 | id = control.id 34 | 35 | control.refs.each do |ref| 36 | next unless ref.url 37 | 38 | next if urls.include?(ref.url) 39 | 40 | urls.push(ref.url) 41 | link = URI.parse(ref.url) 42 | 43 | next unless link.host && link.path 44 | 45 | begin 46 | res = URI.open(ref.url) 47 | puts "#{id} - #{ref.url} - \x1b[32m#{res.status.last}\x1b[0m" 48 | rescue OpenURI::HTTPError => e 49 | broken.push("#{id} - #{ref.url}") 50 | puts "\x1b[35m#{id}\x1b[0m - #{ref.url} (\x1b[35m#{e.message}\x1b[0m)" 51 | end 52 | end 53 | end 54 | 55 | puts "\nChecked #{urls.length} links.\n\n" 56 | 57 | return if broken.empty? 58 | 59 | puts 'Broken links:' 60 | 61 | broken.each do |msg| 62 | puts "#{msg}\n\n" 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /docker/lib/tasks/convert_findings.rake: -------------------------------------------------------------------------------- 1 | namespace :findings do 2 | desc 'Convert Darkbit findings.json into controls.yaml' 3 | task convert: :environment do 4 | convert_findings 5 | end 6 | 7 | def convert_findings 8 | input_file = 'findings.json' 9 | output_file = 'controls.yaml' 10 | 11 | begin 12 | json = JSON.load(File.new(input_file)) 13 | rescue StandardError 14 | puts "Error: missing input file \"#{input_file}\"" 15 | exit 1 16 | end 17 | 18 | # UUID v5 allows for a constant GUID based on a fixed string (title) 19 | # e.g. Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, r['title']) 20 | data = json['results'].map do |r| 21 | { 22 | name: "dbc-#{r['finding']}", 23 | guid: Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, r['title']), 24 | platform: r['platform'].downcase, 25 | title: r['title'], 26 | description: r['description'], 27 | validation: r['validation'] ? "\n#{r['validation']}" : '', 28 | remediation: r['remediation'] ? "\n#{r['remediation']}" : '', 29 | impact: (r['severity'] * 10).to_i, 30 | refs: r['references'], 31 | tags: ['dbc'] 32 | } 33 | end.map { |x| x.deep_stringify_keys } 34 | 35 | output = File.open(output_file, 'w') { |file| file.write(data.to_yaml) } 36 | puts "Wrote #{output.inspect} bytes to #{output_file}." 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /docker/load_dir/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/load_dir/.gitkeep -------------------------------------------------------------------------------- /docker/public/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/public/.keep -------------------------------------------------------------------------------- /docker/public/404.html: -------------------------------------------------------------------------------- 1 | index.html -------------------------------------------------------------------------------- /docker/public/img/logos/opencspm-mark-on-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/public/img/logos/opencspm-mark-on-light.png -------------------------------------------------------------------------------- /docker/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | OpenCSPM - API Server 9 | 10 |
api server
11 | 12 | 13 | -------------------------------------------------------------------------------- /docker/spec/factories/campaigns.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :campaign do 3 | name { "MyString" } 4 | notes { "MyText" } 5 | user { nil } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /docker/spec/factories/controls.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :control do 3 | tag { "MyString" } 4 | title { "MyString" } 5 | description { "MyText" } 6 | impact { 1 } 7 | profile { nil } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker/spec/factories/issues.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :issue do 3 | status { 1 } 4 | result { nil } 5 | resource { nil } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /docker/spec/factories/jobs.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :job do 3 | token { "MyString" } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/factories/profiles.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :profile do 3 | name { "MyString" } 4 | author { "MyString" } 5 | provider { "MyString" } 6 | category { "MyString" } 7 | status { 1 } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker/spec/factories/resources.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :resource do 3 | name { "MyString" } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/factories/results.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :result do 3 | job { nil } 4 | profile { nil } 5 | data { "" } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /docker/spec/factories/sources.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :source do 3 | name { "MyString" } 4 | location { "MyString" } 5 | status { 1 } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /docker/spec/factories/taggings.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :tagging do 3 | tag { nil } 4 | control { nil } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /docker/spec/factories/tags.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :tag do 3 | name { "MyString" } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/jobs/example_job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ExampleJob, type: :job do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/campaign_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Campaign, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/control_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Control, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/identity_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Identity, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/issue_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Issue, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Job, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/organization_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Organization, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/profile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Profile, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/resource_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Resource, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/result_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Result, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/role_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Role, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/source_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Source, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/tag_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Tag, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/tagging_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Tagging, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe User, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/requests/sessions_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "Sessions", type: :request do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /docker/spec/routing/campaigns_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::CampaignsController, type: :routing do 4 | describe 'routing' do 5 | it 'routes to #index' do 6 | expect(get: '/campaigns').to route_to('campaigns#index') 7 | end 8 | 9 | it 'routes to #show' do 10 | expect(get: '/campaigns/1').to route_to('campaigns#show', id: '1') 11 | end 12 | 13 | it 'routes to #create' do 14 | expect(post: '/campaigns').to route_to('campaigns#create') 15 | end 16 | 17 | it 'routes to #update via PUT' do 18 | expect(put: '/campaigns/1').to route_to('campaigns#update', id: '1') 19 | end 20 | 21 | it 'routes to #update via PATCH' do 22 | expect(patch: '/campaigns/1').to route_to('campaigns#update', id: '1') 23 | end 24 | 25 | it 'routes to #destroy' do 26 | expect(delete: '/campaigns/1').to route_to('campaigns#destroy', id: '1') 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /docker/spec/routing/controls_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::ControlsController, type: :routing do 4 | describe 'routing' do 5 | it 'routes to #index' do 6 | expect(get: '/controls').to route_to('controls#index') 7 | end 8 | 9 | it 'routes to #show' do 10 | expect(get: '/controls/1').to route_to('controls#show', id: '1') 11 | end 12 | 13 | it 'routes to #create' do 14 | # expect(post: '/controls').to route_to('controls#create') 15 | expect(post: '/controls').not_to be_routable 16 | end 17 | 18 | it 'routes to #update via PUT' do 19 | # expect(put: '/controls/1').to route_to('controls#update', id: '1') 20 | expect(put: '/controls/1').not_to be_routable 21 | end 22 | 23 | it 'routes to #update via PATCH' do 24 | # expect(patch: '/controls/1').to route_to('controls#update', id: '1') 25 | expect(patch: '/controls/1').not_to be_routable 26 | end 27 | 28 | it 'routes to #destroy' do 29 | # expect(delete: '/controls/1').to route_to('controls#destroy', id: '1') 30 | expect(delete: '/controls/1').not_to be_routable 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /docker/spec/routing/organizations_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::OrganizationsController, type: :routing do 4 | describe 'routing' do 5 | it 'routes to #index' do 6 | expect(get: '/organizations').to route_to('organizations#index') 7 | end 8 | 9 | it 'routes to #show' do 10 | expect(get: '/organizations/1').to route_to('organizations#show', id: '1') 11 | end 12 | 13 | it 'routes to #create' do 14 | expect(post: '/organizations').to route_to('organizations#create') 15 | end 16 | 17 | it 'routes to #update via PUT' do 18 | expect(put: '/organizations/1').to route_to('organizations#update', id: '1') 19 | end 20 | 21 | it 'routes to #update via PATCH' do 22 | expect(patch: '/organizations/1').to route_to('organizations#update', id: '1') 23 | end 24 | 25 | it 'routes to #destroy' do 26 | expect(delete: '/organizations/1').to route_to('organizations#destroy', id: '1') 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /docker/spec/routing/profiles_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::ProfilesController, type: :routing do 4 | describe 'routing' do 5 | it 'routes to #index' do 6 | expect(get: '/profiles').to route_to('profiles#index') 7 | end 8 | 9 | it 'routes to #show' do 10 | expect(get: '/profiles/1').to route_to('profiles#show', id: '1') 11 | end 12 | 13 | it 'routes to #create' do 14 | # expect(post: "/profiles").to route_to("profiles#create") 15 | expect(post: '/profiles').not_to be_routable 16 | end 17 | 18 | it 'routes to #update via PUT' do 19 | # expect(put: "/profiles/1").to route_to("profiles#update", id: "1") 20 | expect(put: '/profiles/1').not_to be_routable 21 | end 22 | 23 | it 'routes to #update via PATCH' do 24 | # expect(patch: "/profiles/1").to route_to("profiles#update", id: "1") 25 | expect(patch: '/profiles/1').not_to be_routable 26 | end 27 | 28 | it 'routes to #destroy' do 29 | # expect(delete: "/profiles/1").to route_to("profiles#destroy", id: "1") 30 | expect(delete: '/profiles/1').not_to be_routable 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /docker/spec/routing/sources_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::SourcesController, type: :routing do 4 | describe 'routing' do 5 | it 'routes to #index' do 6 | expect(get: '/sources').to route_to('sources#index') 7 | end 8 | 9 | it 'routes to #show' do 10 | expect(get: '/sources/1').to route_to('sources#show', id: '1') 11 | end 12 | 13 | it 'routes to #create' do 14 | # expect(post: '/sources').to route_to('sources#create') 15 | expect(post: '/sources').not_to be_routable 16 | end 17 | 18 | it 'routes to #update via PUT' do 19 | expect(put: '/sources/1').to route_to('sources#update', id: '1') 20 | end 21 | 22 | it 'routes to #update via PATCH' do 23 | expect(patch: '/sources/1').to route_to('sources#update', id: '1') 24 | end 25 | 26 | it 'routes to #destroy' do 27 | # expect(delete: "/sources/1").to route_to("sources#destroy", id: "1") 28 | expect(delete: '/sources/1').not_to be_routable 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /docker/spec/routing/users_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::UsersController, type: :routing do 4 | describe 'routing' do 5 | it 'routes to #index' do 6 | expect(get: '/users').to route_to('users#index') 7 | end 8 | 9 | it 'routes to #show' do 10 | expect(get: '/users/1').to route_to('users#show', id: '1') 11 | end 12 | 13 | it 'routes to #create' do 14 | expect(post: '/users').to route_to('users#create') 15 | end 16 | 17 | it 'routes to #update via PUT' do 18 | expect(put: '/users/1').to route_to('users#update', id: '1') 19 | end 20 | 21 | it 'routes to #update via PATCH' do 22 | expect(patch: '/users/1').to route_to('users#update', id: '1') 23 | end 24 | 25 | it 'routes to #destroy' do 26 | expect(delete: '/users/1').to route_to('users#destroy', id: '1') 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /docker/test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase 4 | # test "connects with cookies" do 5 | # cookies.signed[:user_id] = 42 6 | # 7 | # connect 8 | # 9 | # assert_equal connection.user_id, "42" 10 | # end 11 | end 12 | -------------------------------------------------------------------------------- /docker/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/test/controllers/.keep -------------------------------------------------------------------------------- /docker/test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/test/fixtures/.keep -------------------------------------------------------------------------------- /docker/test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/test/fixtures/files/.keep -------------------------------------------------------------------------------- /docker/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/test/integration/.keep -------------------------------------------------------------------------------- /docker/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/test/mailers/.keep -------------------------------------------------------------------------------- /docker/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/test/models/.keep -------------------------------------------------------------------------------- /docker/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require_relative '../config/environment' 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Run tests in parallel with specified workers 7 | parallelize(workers: :number_of_processors) 8 | 9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /docker/ui/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /docker/ui/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /docker/ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ['plugin:vue/essential', 'eslint:recommended'], 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 13 | semi: ['error', 'never'] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docker/ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "singleAttributePerLine": true, 5 | "vueIndentScriptAndStyle": true, 6 | "jsxBracketSameLine": true, 7 | "htmlWhitespaceSensitivity": "ignore", 8 | "printWidth": 80, 9 | "bracketSpacing": true 10 | } 11 | -------------------------------------------------------------------------------- /docker/ui/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docker/ui/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8000", 3 | "viewportHeight": 900, 4 | "viewportWidth": 1400, 5 | "video": false 6 | } 7 | -------------------------------------------------------------------------------- /docker/ui/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /docker/ui/cypress/integration/examples/aliasing.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Aliasing', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io/commands/aliasing') 6 | }) 7 | 8 | it('.as() - alias a DOM element for later use', () => { 9 | // https://on.cypress.io/as 10 | 11 | // Alias a DOM element for use later 12 | // We don't have to traverse to the element 13 | // later in our code, we reference it with @ 14 | 15 | cy.get('.as-table').find('tbody>tr') 16 | .first().find('td').first() 17 | .find('button').as('firstBtn') 18 | 19 | // when we reference the alias, we place an 20 | // @ in front of its name 21 | cy.get('@firstBtn').click() 22 | 23 | cy.get('@firstBtn') 24 | .should('have.class', 'btn-success') 25 | .and('contain', 'Changed') 26 | }) 27 | 28 | it('.as() - alias a route for later use', () => { 29 | // Alias the route to wait for its response 30 | cy.server() 31 | cy.route('GET', 'comments/*').as('getComment') 32 | 33 | // we have code that gets a comment when 34 | // the button is clicked in scripts.js 35 | cy.get('.network-btn').click() 36 | 37 | // https://on.cypress.io/wait 38 | cy.wait('@getComment').its('status').should('eq', 200) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /docker/ui/cypress/integration/examples/location.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Location', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io/commands/location') 6 | }) 7 | 8 | it('cy.hash() - get the current URL hash', () => { 9 | // https://on.cypress.io/hash 10 | cy.hash().should('be.empty') 11 | }) 12 | 13 | it('cy.location() - get window.location', () => { 14 | // https://on.cypress.io/location 15 | cy.location().should((location) => { 16 | expect(location.hash).to.be.empty 17 | expect(location.href).to.eq('https://example.cypress.io/commands/location') 18 | expect(location.host).to.eq('example.cypress.io') 19 | expect(location.hostname).to.eq('example.cypress.io') 20 | expect(location.origin).to.eq('https://example.cypress.io') 21 | expect(location.pathname).to.eq('/commands/location') 22 | expect(location.port).to.eq('') 23 | expect(location.protocol).to.eq('https:') 24 | expect(location.search).to.be.empty 25 | }) 26 | }) 27 | 28 | it('cy.url() - get the current URL', () => { 29 | // https://on.cypress.io/url 30 | cy.url().should('eq', 'https://example.cypress.io/commands/location') 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /docker/ui/cypress/integration/examples/navigation.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Navigation', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io') 6 | cy.get('.navbar-nav').contains('Commands').click() 7 | cy.get('.dropdown-menu').contains('Navigation').click() 8 | }) 9 | 10 | it('cy.go() - go back or forward in the browser\'s history', () => { 11 | // https://on.cypress.io/go 12 | 13 | cy.location('pathname').should('include', 'navigation') 14 | 15 | cy.go('back') 16 | cy.location('pathname').should('not.include', 'navigation') 17 | 18 | cy.go('forward') 19 | cy.location('pathname').should('include', 'navigation') 20 | 21 | // clicking back 22 | cy.go(-1) 23 | cy.location('pathname').should('not.include', 'navigation') 24 | 25 | // clicking forward 26 | cy.go(1) 27 | cy.location('pathname').should('include', 'navigation') 28 | }) 29 | 30 | it('cy.reload() - reload the page', () => { 31 | // https://on.cypress.io/reload 32 | cy.reload() 33 | 34 | // reload the page without using the cache 35 | cy.reload(true) 36 | }) 37 | 38 | it('cy.visit() - visit a remote url', () => { 39 | // https://on.cypress.io/visit 40 | 41 | // Visit any sub-domain of your current domain 42 | 43 | // Pass options to the visit 44 | cy.visit('https://example.cypress.io/commands/navigation', { 45 | timeout: 50000, // increase total time for the visit to resolve 46 | onBeforeLoad (contentWindow) { 47 | // contentWindow is the remote page's window object 48 | expect(typeof contentWindow === 'object').to.be.true 49 | }, 50 | onLoad (contentWindow) { 51 | // contentWindow is the remote page's window object 52 | expect(typeof contentWindow === 'object').to.be.true 53 | }, 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /docker/ui/cypress/integration/examples/viewport.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Viewport', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io/commands/viewport') 6 | }) 7 | 8 | it('cy.viewport() - set the viewport size and dimension', () => { 9 | // https://on.cypress.io/viewport 10 | 11 | cy.get('#navbar').should('be.visible') 12 | cy.viewport(320, 480) 13 | 14 | // the navbar should have collapse since our screen is smaller 15 | cy.get('#navbar').should('not.be.visible') 16 | cy.get('.navbar-toggle').should('be.visible').click() 17 | cy.get('.nav').find('a').should('be.visible') 18 | 19 | // lets see what our app looks like on a super large screen 20 | cy.viewport(2999, 2999) 21 | 22 | // cy.viewport() accepts a set of preset sizes 23 | // to easily set the screen to a device's width and height 24 | 25 | // We added a cy.wait() between each viewport change so you can see 26 | // the change otherwise it is a little too fast to see :) 27 | 28 | cy.viewport('macbook-15') 29 | cy.wait(200) 30 | cy.viewport('macbook-13') 31 | cy.wait(200) 32 | cy.viewport('macbook-11') 33 | cy.wait(200) 34 | cy.viewport('ipad-2') 35 | cy.wait(200) 36 | cy.viewport('ipad-mini') 37 | cy.wait(200) 38 | cy.viewport('iphone-6+') 39 | cy.wait(200) 40 | cy.viewport('iphone-6') 41 | cy.wait(200) 42 | cy.viewport('iphone-5') 43 | cy.wait(200) 44 | cy.viewport('iphone-4') 45 | cy.wait(200) 46 | cy.viewport('iphone-3') 47 | cy.wait(200) 48 | 49 | // cy.viewport() accepts an orientation for all presets 50 | // the default orientation is 'portrait' 51 | cy.viewport('ipad-2', 'portrait') 52 | cy.wait(200) 53 | cy.viewport('iphone-4', 'landscape') 54 | cy.wait(200) 55 | 56 | // The viewport will be reset back to the default dimensions 57 | // in between tests (the default can be set in cypress.json) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /docker/ui/cypress/integration/examples/waiting.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Waiting', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io/commands/waiting') 6 | }) 7 | // BE CAREFUL of adding unnecessary wait times. 8 | // https://on.cypress.io/best-practices#Unnecessary-Waiting 9 | 10 | // https://on.cypress.io/wait 11 | it('cy.wait() - wait for a specific amount of time', () => { 12 | cy.get('.wait-input1').type('Wait 1000ms after typing') 13 | cy.wait(1000) 14 | cy.get('.wait-input2').type('Wait 1000ms after typing') 15 | cy.wait(1000) 16 | cy.get('.wait-input3').type('Wait 1000ms after typing') 17 | cy.wait(1000) 18 | }) 19 | 20 | it('cy.wait() - wait for a specific route', () => { 21 | cy.server() 22 | 23 | // Listen to GET to comments/1 24 | cy.route('GET', 'comments/*').as('getComment') 25 | 26 | // we have code that gets a comment when 27 | // the button is clicked in scripts.js 28 | cy.get('.network-btn').click() 29 | 30 | // wait for GET comments/1 31 | cy.wait('@getComment').its('status').should('eq', 200) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /docker/ui/cypress/integration/examples/window.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Window', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io/commands/window') 6 | }) 7 | 8 | it('cy.window() - get the global window object', () => { 9 | // https://on.cypress.io/window 10 | cy.window().should('have.property', 'top') 11 | }) 12 | 13 | it('cy.document() - get the document object', () => { 14 | // https://on.cypress.io/document 15 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8') 16 | }) 17 | 18 | it('cy.title() - get the title', () => { 19 | // https://on.cypress.io/title 20 | cy.title().should('include', 'Kitchen Sink') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /docker/ui/cypress/integration/smoke_spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe('Smoke Test', () => { 4 | // runs once before all tests in the block 5 | before(() => { 6 | cy.clearCookies() 7 | cy.login() 8 | }) 9 | 10 | // maintain current session through smoke tests 11 | beforeEach(() => { 12 | Cypress.Cookies.preserveOnce('_opencspm_session', '_opencspm_token') 13 | }) 14 | 15 | it('can see current campaigns', () => { 16 | cy.contains('Campaigns').click() 17 | cy.contains('Campaigns allow you to track the progress of controls you care about.') 18 | cy.url().should('include', '/campaigns') 19 | }) 20 | 21 | it('can see current profiles and view one', () => { 22 | cy.contains('Profiles').click() 23 | cy.url().should('include', '/profiles') 24 | cy.contains('All Kubernetes').click() 25 | cy.contains('Send to campaign') 26 | cy.contains(/^Control \(\d*\)/) 27 | cy.url().should('include', '/controls') 28 | }) 29 | 30 | it('can see current data sources', () => { 31 | cy.contains('Sources').click() 32 | cy.url().should('include', '/sources') 33 | cy.contains('Inventory Sources') 34 | }) 35 | 36 | it('can see all controls and create a campaign', () => { 37 | cy.contains('Controls').click() 38 | cy.url().should('include', '/controls') 39 | cy.contains(/^Control \(\d*\)/) 40 | cy.contains('Send to campaign').click() 41 | cy.contains(/Send \d* controls to campaign+/).click() 42 | cy.contains('New Campaign') 43 | cy.url().should('include', '/campaigns') 44 | }) 45 | 46 | it('can see admin details', () => { 47 | cy.contains('Admin').click() 48 | cy.url().should('include', '/admin') 49 | cy.contains('Recent activity') 50 | }) 51 | 52 | }) 53 | -------------------------------------------------------------------------------- /docker/ui/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | } 22 | -------------------------------------------------------------------------------- /docker/ui/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | 27 | // OpenCSPM 28 | // 29 | // manual login 30 | Cypress.Commands.add('login', () => { 31 | cy.visit('/') 32 | cy.contains('h2', 'OpenCSPM') 33 | cy.contains(/Demo Admin Sign in/).click() 34 | }) 35 | -------------------------------------------------------------------------------- /docker/ui/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /docker/ui/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Run `yarn install` once on first run 4 | # 5 | 6 | FILE=".init" 7 | MSG="[yarn] First run detected, installing..." 8 | CMD="yarn install" 9 | RUN="yarn serve" 10 | 11 | if [[ -f ${FILE} ]]; then 12 | ${RUN} 13 | else 14 | echo ${MSG} 15 | ${CMD} && touch ${FILE} 16 | ${RUN} 17 | fi 18 | -------------------------------------------------------------------------------- /docker/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opencspm", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "smoke": "cypress run --browser chrome --spec cypress/integration/smoke_spec.js --headless" 10 | }, 11 | "dependencies": { 12 | "@tailwindcss/ui": "^0.7.2", 13 | "axios": "^0.21.1", 14 | "chart.js": "^2.9.3", 15 | "core-js": "^3.6.5", 16 | "highlight.js": "^10.4.1", 17 | "json2csv": "^5.0.1", 18 | "marked": "^2.0.0", 19 | "moment": "^2.27.0", 20 | "pluralize": "^8.0.0", 21 | "tailwindcss": "^1.7.6", 22 | "vue": "^2.6.11", 23 | "vue-chartjs": "^3.5.1", 24 | "vue-router": "^3.2.0" 25 | }, 26 | "devDependencies": { 27 | "@vue/cli-plugin-babel": "~4.5.0", 28 | "@vue/cli-plugin-eslint": "~4.5.0", 29 | "@vue/cli-plugin-router": "~4.5.0", 30 | "@vue/cli-service": "~4.5.0", 31 | "@vue/eslint-config-standard": "^5.1.2", 32 | "babel-eslint": "^10.1.0", 33 | "eslint": "^6.7.2", 34 | "eslint-plugin-import": "^2.20.2", 35 | "eslint-plugin-node": "^11.1.0", 36 | "eslint-plugin-promise": "^4.2.1", 37 | "eslint-plugin-standard": "^4.0.0", 38 | "eslint-plugin-vue": "^6.2.2", 39 | "vue-template-compiler": "^2.6.11" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docker/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('tailwindcss'), require('autoprefixer')] 3 | } 4 | -------------------------------------------------------------------------------- /docker/ui/public/app.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const fs = require('fs') 3 | const httpPort = 8000 4 | 5 | http.createServer((req, res) => { 6 | fs.readFile('index.html', 'utf-8', (err, content) => { 7 | if (err) { 8 | console.log('OpenCSPM frontend cannot open "index.html" file.') 9 | } 10 | 11 | res.writeHead(200, { 12 | 'Content-Type': 'text/html; charset=utf-8' 13 | }) 14 | 15 | res.end(content) 16 | }) 17 | }).listen(httpPort, () => { 18 | console.log('OpenCSPM frontend server listening on: http://localhost:%s', httpPort) 19 | }) 20 | -------------------------------------------------------------------------------- /docker/ui/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/ui/public/favicon.png -------------------------------------------------------------------------------- /docker/ui/public/img/logos/opencspm-mark-on-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/ui/public/img/logos/opencspm-mark-on-light.png -------------------------------------------------------------------------------- /docker/ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | <%= htmlWebpackPlugin.options.title %> 13 | 14 | 15 | 16 | 20 |
21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docker/ui/readme.md: -------------------------------------------------------------------------------- 1 | OpenCSPM demo web ui 2 | -------------------------------------------------------------------------------- /docker/ui/src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docker/ui/src/assets/css/custom.css: -------------------------------------------------------------------------------- 1 | /* Fonts */ 2 | @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap'); 3 | @import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap'); 4 | 5 | /* Other styling */ 6 | 7 | .graph { 8 | /* padding: 20px; */ 9 | /* border: 1px #e1e4e8 solid; */ 10 | /* margin: 20px; */ 11 | } 12 | 13 | .days li:nth-child(odd) { 14 | visibility: hidden; 15 | } 16 | 17 | /* Charts */ 18 | .chart-loader { 19 | min-height: 25rem; 20 | } 21 | 22 | /* Tooltips */ 23 | .tooltip .tooltip-text { 24 | visibility: hidden; 25 | text-align: center; 26 | padding: 0.25rem 1rem; 27 | position: absolute; 28 | z-index: 100; 29 | } 30 | .tooltip:hover .tooltip-text { 31 | visibility: visible; 32 | } 33 | 34 | /* Markdown code blocks */ 35 | .remediation-details p, 36 | .validation-details p { 37 | margin-top: 0.5rem; 38 | margin-bottom: 0.5rem; 39 | } 40 | 41 | .remediation-details p a, 42 | .validation-details p a { 43 | font-weight: bold; 44 | text-decoration: underline; 45 | } 46 | 47 | .control-modal code, 48 | .control-modal pre { 49 | font-size: 0.75rem; 50 | border-radius: 0.2rem; 51 | } 52 | 53 | .control-modal code { 54 | padding-left: 0.25rem; 55 | padding-right: 0.25rem; 56 | padding-top: 0.1rem; 57 | padding-bottom: 0.1rem; 58 | background-color: #e0e0e0; 59 | color: rgba(52, 58, 64, 1); 60 | font-family: 'Source Code Pro'; 61 | } 62 | 63 | .control-modal pre, 64 | .control-modal pre code { 65 | padding: 0.25rem; 66 | --bg-opacity: 1; 67 | background-color: rgba(37, 47, 63, var(--bg-opacity)); 68 | --text-opacity: 1; 69 | color: rgba(141, 162, 251, var(--text-opacity)); 70 | } 71 | 72 | .control-modal pre code { 73 | margin-left: -0.25rem; 74 | } 75 | 76 | .control-modal p code { 77 | padding-top: 0.05rem; 78 | padding-bottom: 0.05rem; 79 | } 80 | 81 | .control-modal ul { 82 | margin-left: 0.5rem; 83 | } 84 | 85 | .control-modal li { 86 | margin-top: 0.5rem; 87 | margin-bottom: 0.5rem; 88 | } 89 | -------------------------------------------------------------------------------- /docker/ui/src/assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /docker/ui/src/assets/logo_goes_here.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/docker/ui/src/assets/logo_goes_here.png -------------------------------------------------------------------------------- /docker/ui/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | -------------------------------------------------------------------------------- /docker/ui/src/components/campaign/NewCampaign.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /docker/ui/src/components/profile/ProfileSearch.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /docker/ui/src/components/profile/Profiles.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 49 | -------------------------------------------------------------------------------- /docker/ui/src/components/result/Results.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /docker/ui/src/components/shared/BarChart.js: -------------------------------------------------------------------------------- 1 | import { 2 | Bar 3 | } from 'vue-chartjs' 4 | export default { 5 | extends: Bar, 6 | props: { 7 | chartData: { 8 | type: Array, 9 | required: false 10 | }, 11 | chartLabels: { 12 | type: Array, 13 | required: true 14 | } 15 | }, 16 | data() { 17 | return { 18 | options: { 19 | scales: { 20 | yAxes: [{ 21 | ticks: { 22 | beginAtZero: true, 23 | stepSize: 1, 24 | suggestedMax: 10 25 | }, 26 | gridLines: { 27 | display: true 28 | } 29 | }], 30 | xAxes: [{ 31 | ticks: { 32 | padding: 0, 33 | display: true, 34 | maxTicksLimit: 15, 35 | }, 36 | gridLines: { 37 | display: false, 38 | } 39 | }] 40 | }, 41 | legend: { 42 | display: false 43 | }, 44 | responsive: true, 45 | maintainAspectRatio: false 46 | } 47 | } 48 | }, 49 | mounted() { 50 | this.renderChart({ 51 | labels: this.chartLabels, 52 | datasets: [{ 53 | label: 'Failed controls', 54 | borderColor: '#249EBF', 55 | borderWidth: 1, 56 | pointBorderColor: '#249EBF', 57 | backgroundColor: '#76a9fa', 58 | data: this.chartData 59 | }] 60 | }, 61 | this.options 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docker/ui/src/components/shared/BarChartChartjs.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /docker/ui/src/components/shared/IconFilter.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /docker/ui/src/components/shared/NotFound.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /docker/ui/src/components/shared/StatusDot.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 38 | -------------------------------------------------------------------------------- /docker/ui/src/components/shared/Tag.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /docker/ui/src/components/shared/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /docker/ui/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import axios from 'axios' 5 | 6 | Vue.config.productionTip = false 7 | 8 | // api url 9 | // customize for self-hosted environment 10 | const API_BASE_URL = 11 | process.env.NODE_ENV === 'production' ? 'http://localhost:5000/api' : 'http://localhost:5000/api' 12 | 13 | // base url for oauth links 14 | // customize for self-hosted environment 15 | Vue.prototype.BASE_URL = 16 | process.env.NODE_ENV === 'production' ? 'https://auth.opencspm.org' : 'http://localhost:5000' 17 | 18 | // axios defaults 19 | axios.defaults.baseURL = API_BASE_URL 20 | axios.defaults.withCredentials = true 21 | axios.defaults.xsrfCookieName = '_opencspm_token' 22 | axios.defaults.xsrfHeaderName = 'x-csrf-token' 23 | 24 | Vue.prototype.$http = axios 25 | 26 | new Vue({ 27 | router, 28 | render: h => h(App) 29 | }).$mount('#app') 30 | -------------------------------------------------------------------------------- /docker/ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | module.exports = { 4 | future: { 5 | removeDeprecatedGapUtilities: true, 6 | purgeLayersByDefault: true 7 | }, 8 | purge: ['./src/**/*.html', './src/**/*.vue'], 9 | theme: { 10 | extend: { 11 | scale: { 12 | mirror: '-1' 13 | }, 14 | maxHeight: { 15 | xs: '20rem', 16 | sm: '24rem', 17 | md: '28rem', 18 | lg: '32rem', 19 | xl: '36rem', 20 | '2xl': '42rem', 21 | '3xl': '48rem', 22 | '4xl': '56rem' 23 | }, 24 | maxWidth: { 25 | '4': '4rem' 26 | }, 27 | spacing: { 28 | '72': '18rem', 29 | '80': '20rem' 30 | }, 31 | padding: { 32 | '5/6': '83.3333%' 33 | }, 34 | fontFamily: { 35 | // sans: ['Inter var', ...defaultTheme.fontFamily.sans] 36 | sans: ['Nunito', ...defaultTheme.fontFamily.sans] 37 | } 38 | } 39 | }, 40 | variants: { 41 | visibility: ['group-hover'] 42 | }, 43 | plugins: [ 44 | require('@tailwindcss/ui')({ 45 | layout: 'sidebar' 46 | }) 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /site/img/docker-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/site/img/docker-dashboard.png -------------------------------------------------------------------------------- /site/img/high-level-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/site/img/high-level-arch.png -------------------------------------------------------------------------------- /site/img/opencspm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/site/img/opencspm-logo.png -------------------------------------------------------------------------------- /site/img/opencspm-quickstart-keyframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/site/img/opencspm-quickstart-keyframe.png -------------------------------------------------------------------------------- /site/img/opencspm-s3-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSPM/opencspm/6af73383d38722bbd239d1ce41f39482b33832d5/site/img/opencspm-s3-search.gif --------------------------------------------------------------------------------