├── .gitignore ├── .gitpod.yml ├── Gemfile ├── Gemfile.lock ├── LLM-Prompt.txt ├── README.md ├── cloud_project_bootcamp_validation_tool.gemspec ├── dsl.example ├── examples ├── .env.example ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── aws_2023 │ ├── dynamic_params.rb │ ├── permissioner.rb │ ├── puller.rb │ ├── specific_params.rb │ ├── state.rb │ ├── validations │ │ ├── authenication.rb │ │ ├── cicd.rb │ │ ├── cluster.rb │ │ ├── db.rb │ │ ├── ddb.rb │ │ ├── iac.rb │ │ ├── networking.rb │ │ ├── serverless.rb │ │ └── static_website_hosting.rb │ └── validator.rb ├── azure │ ├── dynamic_params.rb │ ├── puller.rb │ ├── specific_params.rb │ ├── state.rb │ ├── validations │ │ └── example.rb │ └── validator.rb ├── bin │ └── deploy ├── gcp │ ├── dynamic_params.rb │ ├── puller.rb │ ├── specific_params.rb │ ├── state.rb │ ├── validations │ │ └── example.rb │ └── validator.rb ├── output │ └── .keep ├── specific_params └── tf_beginner_aws_2023 │ └── validations │ ├── s3.rb │ └── week1.rb ├── lib ├── cpbvt.rb └── cpbvt │ ├── downloader.rb │ ├── manifest.rb │ ├── module_defs.rb │ ├── payloads │ ├── aws │ │ ├── command.rb │ │ ├── commands │ │ │ ├── acm.rb │ │ │ ├── apigatewayv2.rb │ │ │ ├── cloudformation.rb │ │ │ ├── cloudfront.rb │ │ │ ├── codebuild.rb │ │ │ ├── codepipeline.rb │ │ │ ├── cognito_idp.rb │ │ │ ├── dynamodb.rb │ │ │ ├── dynamodbstreams.rb │ │ │ ├── ec2.rb │ │ │ ├── ecr.rb │ │ │ ├── ecs.rb │ │ │ ├── elbv2.rb │ │ │ ├── lambda.rb │ │ │ ├── rds.rb │ │ │ ├── route53.rb │ │ │ ├── s3api.rb │ │ │ └── servicediscovery.rb │ │ ├── cross-account-role-template.yaml │ │ ├── extractor.rb │ │ ├── extractors │ │ │ ├── acm.rb │ │ │ ├── apigatewayv2.rb │ │ │ ├── cloudformation.rb │ │ │ ├── cloudfront.rb │ │ │ ├── codebuild.rb │ │ │ ├── codepipeline.rb │ │ │ ├── cognito_idp.rb │ │ │ ├── dynamodb.rb │ │ │ ├── dynamodbstreams.rb │ │ │ ├── ecr.rb │ │ │ ├── ecs.rb │ │ │ ├── elbv2.rb │ │ │ ├── lambda.rb │ │ │ ├── route53.rb │ │ │ └── s3api.rb │ │ ├── general_params.rb │ │ ├── policies │ │ │ ├── acm.rb │ │ │ ├── apigatewayv2.rb │ │ │ ├── cloudformation.rb │ │ │ ├── cloudfront.rb │ │ │ ├── codebuild.rb │ │ │ ├── codepipeline.rb │ │ │ ├── cognito_idp.rb │ │ │ ├── dynamodb.rb │ │ │ ├── dynamodbstreams.rb │ │ │ ├── ec2.rb │ │ │ ├── ecr.rb │ │ │ ├── ecs.rb │ │ │ ├── elbv2.rb │ │ │ ├── lambda.rb │ │ │ ├── rds.rb │ │ │ ├── route53.rb │ │ │ ├── s3api.rb │ │ │ └── servicediscovery.rb │ │ ├── policy.rb │ │ └── runner.rb │ ├── azure │ │ ├── command.rb │ │ ├── commands │ │ │ ├── function_app.rb │ │ │ ├── group.rb │ │ │ ├── network.rb │ │ │ ├── storage.rb │ │ │ └── virtual_machine.rb │ │ ├── general_params.rb │ │ ├── lighthouse-template.json │ │ ├── policy.rb │ │ └── runner.rb │ └── gcp │ │ ├── command.rb │ │ ├── commands │ │ └── storage.rb │ │ ├── general_params.rb │ │ └── runner.rb │ ├── tester │ ├── Readme.md │ ├── assert_cfn_resource.rb │ ├── assert_find.rb │ ├── assert_json.rb │ ├── assert_load.rb │ ├── assert_select.rb │ ├── describe.rb │ ├── report.rb │ ├── runner.rb │ └── spec.rb │ ├── uploader.rb │ └── version.rb └── playground ├── cli-azure.rb └── cli-gcp.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | *.zip 14 | .env 15 | 16 | output/**/* 17 | output/**/**/* 18 | output/**/**/**/* 19 | output/**/**/**/**/* 20 | output/**/**/**/**/**/* 21 | output/**/*.json 22 | /output/**/*.json 23 | examples/output/**/*.json 24 | examples/output/**/*.yaml 25 | output/**/*.yaml 26 | /output/**/*.yaml 27 | 28 | # Used by dotenv library to load environment variables. 29 | # .env 30 | 31 | # Ignore Byebug command history file. 32 | .byebug_history 33 | 34 | ## Specific to RubyMotion: 35 | .dat* 36 | .repl_history 37 | build/ 38 | *.bridgesupport 39 | build-iPhoneOS/ 40 | build-iPhoneSimulator/ 41 | 42 | ## Specific to RubyMotion (use of CocoaPods): 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # vendor/Pods/ 49 | 50 | ## Documentation cache and generated files: 51 | /.yardoc/ 52 | /_yardoc/ 53 | /doc/ 54 | /rdoc/ 55 | 56 | ## Environment normalization: 57 | /.bundle/ 58 | /vendor/bundle 59 | /lib/bundler/man/ 60 | 61 | # for a library or gem, you might want to ignore these files since the code is 62 | # intended to run in multiple environments; otherwise, check them in: 63 | # Gemfile.lock 64 | # .ruby-version 65 | # .ruby-gemset 66 | 67 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 68 | .rvmrc 69 | 70 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 71 | # .rubocop-https?--* 72 | 73 | gcp-service-account-key.json -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: ruby 3 | init: | 4 | bundle install 5 | cd examples 6 | bundle install 7 | - name: gcloud-cli 8 | init: | 9 | sudo apt-get install apt-transport-https ca-certificates gnupg -y 10 | echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list 11 | curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - 12 | sudo apt-get update && sudo apt-get install google-cloud-cli -y 13 | - name: azure-cli 14 | init: | 15 | curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash 16 | - name: aws-cli 17 | init: | 18 | cd /workspace 19 | curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" 20 | unzip awscliv2.zip 21 | sudo ./aws/install 22 | cd $THEIA_WORKSPACE_ROOT 23 | vscode: 24 | extensions: 25 | - amazonwebservices.aws-toolkit-vscode 26 | - wingrunr21.vscode-ruby 27 | - rebornix.ruby 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cloud_project_bootcamp_validation_tool (1.4.5) 5 | activemodel 6 | aws-sdk-s3 7 | open3 8 | pry 9 | ruby-openai 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | activemodel (7.0.5) 15 | activesupport (= 7.0.5) 16 | activesupport (7.0.5) 17 | concurrent-ruby (~> 1.0, >= 1.0.2) 18 | i18n (>= 1.6, < 2) 19 | minitest (>= 5.1) 20 | tzinfo (~> 2.0) 21 | aws-eventstream (1.2.0) 22 | aws-partitions (1.769.0) 23 | aws-sdk-core (3.173.1) 24 | aws-eventstream (~> 1, >= 1.0.2) 25 | aws-partitions (~> 1, >= 1.651.0) 26 | aws-sigv4 (~> 1.5) 27 | jmespath (~> 1, >= 1.6.1) 28 | aws-sdk-kms (1.64.0) 29 | aws-sdk-core (~> 3, >= 3.165.0) 30 | aws-sigv4 (~> 1.1) 31 | aws-sdk-s3 (1.122.0) 32 | aws-sdk-core (~> 3, >= 3.165.0) 33 | aws-sdk-kms (~> 1) 34 | aws-sigv4 (~> 1.4) 35 | aws-sigv4 (1.5.2) 36 | aws-eventstream (~> 1, >= 1.0.2) 37 | coderay (1.1.3) 38 | concurrent-ruby (1.2.2) 39 | faraday (2.7.5) 40 | faraday-net_http (>= 2.0, < 3.1) 41 | ruby2_keywords (>= 0.0.4) 42 | faraday-multipart (1.0.4) 43 | multipart-post (~> 2) 44 | faraday-net_http (3.0.2) 45 | i18n (1.14.1) 46 | concurrent-ruby (~> 1.0) 47 | jmespath (1.6.2) 48 | method_source (1.0.0) 49 | minitest (5.18.0) 50 | multipart-post (2.3.0) 51 | open3 (0.1.2) 52 | pry (0.14.2) 53 | coderay (~> 1.1) 54 | method_source (~> 1.0) 55 | ruby-openai (4.1.0) 56 | faraday (>= 1) 57 | faraday-multipart (>= 1) 58 | ruby2_keywords (0.0.5) 59 | tzinfo (2.0.6) 60 | concurrent-ruby (~> 1.0) 61 | 62 | PLATFORMS 63 | arm64-darwin-21 64 | x86_64-linux 65 | 66 | DEPENDENCIES 67 | cloud_project_bootcamp_validation_tool! 68 | 69 | BUNDLED WITH 70 | 2.4.9 71 | -------------------------------------------------------------------------------- /LLM-Prompt.txt: -------------------------------------------------------------------------------- 1 | --- 2 | Only provide the necessary response, please don't describe anything. Do you understand? yes or no? 3 | --- 4 | I am going to provide you a Codeblock Critera and an AWS Architecture and the a following up question. 5 | --- 6 | Codeblock Critera: 7 | - Exclude code comments and empty lines. 8 | - Exclude filters or values from the commands. 9 | - Exclude any commands related to resource creation. 10 | --- 11 | AWS Architecture: 12 | - A custom VPC with three public subnets that route on to the internet, all AWS resources attempt to use this custom VPC 13 | - An ECS Fargate cluster running a single service for the backend flask application 14 | - The backend flask application container image is hosted in a private ECR repo 15 | - The target services are using ECS Connect 16 | - A custom domain name that is being managed by Route53 17 | - An internet facing application load balancer is serving the backend flask application a subnet for the custom domain of api. The backend flask application runs on port 4567 18 | - The target group for the backend flask application is performing health checks 19 | - A CodePipeline for deploying code changes that is sourcing from Github 20 | - The CodePipeline has a build step using CodeBuild to build a docker image and push it our private ECR repo for the backend-flask application container image 21 | - An RDS Postgres server that is internet available in public subnets which is used by the backend flask application 22 | - The CodePipline deploy steps uses ECS deployment not CodeDeploy 23 | - A DynamoDB table that has a DynamoDB stream which triggers a lambda to write updates to the same DynamoDB table 24 | - A Cognito User Pool used for authentication in our web-application, we have a post-configuration lambda webhook that inserts a new user into our RDS Postgres database 25 | - We have a frontend react static website hosted on in an S3 bucket which is only accessible via a CloudFront distribution which redirects to HTTPS. 26 | - The CloudFront distribution for the static website hosting is for both the www. And naked domain 27 | - The application load balancer, and cloudfront distributions all use HTTPS and is using a public certificate generated by Amazon Certification Manager 28 | - Our application has profile photos which are uploaded client side, we do this by having an HTTP API Gateway endpoint which will point to a lambda that will generate out a presigned URL to bucket that will raw assets 29 | - Our HTTP API Gateway uses a Lambda authorization for our Cognito User Pool 30 | - In our HTTP API Gateway we use a proxy to a custom lambda to handle the CORS preflight check 31 | - When new files are uploaded to the bucket holding raw assets this will trigger via S3 Event Notifications a lambda which will process the images into thumbnails and it will output the the images into another bucket which will called the assets buckets 32 | - This assets bucket is only accessible via a cloudfront distribution that will server assets from the assets. subdomain 33 | - Route53 is pointing the naked domain and www to the cloudfront distribution that servers that static frontend react application, where the api. Is serving the backend flask application where as the assets. Subdomain is serving the cloudfront distribution for the graphical assets 34 | --- 35 | Please provide a single codeblock of AWS CLI commands for the provided AWS Architecture following the provided Codeblock Criteria 36 | 37 | --- output 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloud-Project-Bootcamp-Validation-Tool 2 | Cloud Project Bootcamp Validation Tool (CPBVT) is used to determine if a bootcamper has actually done the work within their own cloud account. 3 | -------------------------------------------------------------------------------- /cloud_project_bootcamp_validation_tool.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "cpbvt/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "cloud_project_bootcamp_validation_tool" 9 | s.version = Cpbvt::VERSION 10 | s.authors = ["ExamPro"] 11 | s.email = ["andrew@exampro.co"] 12 | s.homepage = "https://www.exampro.co" 13 | s.summary = 'Cloud Project Bootcamp Validation Tool' 14 | s.description = 'Cloud Project Bootcamp Validation Tool' 15 | s.license = "MIT" 16 | 17 | s.add_dependency 'ruby-openai' 18 | s.add_dependency 'aws-sdk-s3' 19 | s.add_dependency 'activemodel' 20 | s.add_dependency 'pry' 21 | s.add_dependency 'open3' 22 | 23 | s.files = Dir["{app,config,db,lib}/**/*", "README.md"] 24 | s.test_files = Dir["spec/**/*"] 25 | end -------------------------------------------------------------------------------- /dsl.example: -------------------------------------------------------------------------------- 1 | # a possible DSL for pulling all resources. 2 | 3 | 4 | pull :primary do 5 | acm_list_certificates 6 | apigatewayv2_get_apis 7 | cloudformation_list_stacks 8 | codebuild_list_projects 9 | codebuild_list_builds 10 | codepipeline_list_pipelines 11 | cognito_list_user_pools 12 | dynamodb_list_tables 13 | dynamodbstreams_list_streams 14 | ec2_describe_vpcs 15 | ec2_describe_subnets 16 | ec2_describe_route_tables 17 | ec2_describe_internet_gateways 18 | ec2_describe_security_groups 19 | ec2_describe_route_tables 20 | ecr_describe_repositories 21 | ecs_describe_clusters 22 | ecs_list_task_definitions 23 | elbv2_describe_load_balancers 24 | elbv2_describe_target_groups 25 | lambda_list_functions 26 | lambda_list_layers 27 | rds_describe_db_instances 28 | rds_describe_db_subnet_groups 29 | rds_describe_db_snapshots 30 | route53_list_hosted_zones 31 | servicediscovery_list_services 32 | servicediscovery_list_namespaces 33 | 34 | all__acm_describe_certificate 35 | all__apigatewayv2_get_authorizers 36 | all__apigatewayv2_get_integrations 37 | all__cloudformation_list_stack_resources 38 | all__codepipeline_get_pipeline 39 | all__cognito_idp_describe_user_pool 40 | all__cognito_idp_list_user_pool_clients 41 | all__cognito_idp_list_users 42 | all__dynamodb_describe_table 43 | all__dynamodbstreams_describe_stream 44 | all__ecs_describe_services 45 | all__ecr_describe_images 46 | all__elbv2_describe_listeners 47 | all__elbv2_describe_load_balancer_attributes 48 | all__elbv2_describe_target_group_attributes 49 | all__lambda_get_function 50 | all__route53_get_hosted_zone 51 | all__route53_list_resource_record_sets 52 | end 53 | 54 | pull :us_east_1 do 55 | acm_list_certificates 56 | end 57 | 58 | pull :global do 59 | cloudfront_list_distributions 60 | s3api_list_buckets 61 | 62 | all__cloudfront_get_distribution 63 | all__cloudfront_list_invalidations 64 | all__s3api_get_bucket_notification_configuration 65 | all__s3api_get_bucket_policy 66 | all__s3api_get_bucket_cors 67 | all__s3api_get_bucket_website 68 | all__s3api_get_public_access_block 69 | end -------------------------------------------------------------------------------- /examples/.env.example: -------------------------------------------------------------------------------- 1 | # the region which that validation tool runs within 2 | AWS_REGION='us-east-1' 3 | # the aws credentials for the user which run the validation tool 4 | AWS_ACCESS_KEY_ID= 5 | AWS_SECRET_ACCESS_KEY= 6 | 7 | # the s3 bucket that contains json files describing the infrastructure 8 | # so we can perform validation. The name of the bucket no the bucket address 9 | PAYLOADS_BUCKET='cpbvt-payloads' 10 | OUTPUT_PATH='/workspace/cloud-project-bootcamp-validation-tool/examples/output' 11 | 12 | # Target account = bootcamper AWS Account 13 | TARGET_AWS_ACCOUNT_ID='387543059434' 14 | 15 | # Source account = the account that is asking permission to access the bootcamper AWS Account 16 | # This is account where the validation tool is hosted. 17 | SOURCE_AWS_ACCOUNT_ID='387543059434' 18 | 19 | EXTERNAL_ID="TEST123" 20 | 21 | # Specific Params --------- 22 | USER_UUID='da124fec-133b-45c5-8423-04b768c886c2' 23 | 24 | # AWS Specific Params ---- 25 | USER_AWS_REGION='ca-central-1' 26 | NAKED_DOMAIN_NAME='cruddur.com' 27 | GITHUB_FULL_REPO_NAME='omenking/aws-bootcamp-cruddur-2023' 28 | CLUSTER_NAME='CrdClusterFargateCluster' 29 | COGNITO_USER_POOL_NAME='cruddur-user-pool' 30 | APIGATEWAY_API_ID='clbx5fa2yb' 31 | RAW_ASSETS_BUCKET_NAME='cruddur-uploaded-avatars' 32 | BACKEND_FAMILY='backend-flask' 33 | ECS_SERVICE_NAME='backend-flask' 34 | ECR_REPO_NAME='backend-flask' 35 | CFN_STACK_NAME_MACHINEUSER='CrdMachineUser' 36 | CFN_STACK_NAME_BACKEND='CrdSrvBackendFlask' 37 | CFN_STACK_NAME_SYNC='CrdSyncRole' 38 | CFN_STACK_NAME_FRONTEND='CrdFrontend' 39 | CFN_STACK_NAME_CICD='CrdCicd' 40 | CFN_STACK_NAME_DB='CrdDb' 41 | CFN_STACK_NAME_DDB='CrdDdb' 42 | CFN_STACK_NAME_CLUSTER='CrdCluster' 43 | CFN_STACK_NAME_NETWORKING='CrdNet' 44 | CFN_STACK_NAME_CDK='ThumbingServerlessCdkStack' 45 | # Azure Specific Params ---- 46 | AZ_STORAGE_ACCOUNT_NAME='azvalcheckexp' 47 | AZ_STORAGE_CONTAINER_NAME='mycontainer' 48 | AZ_STORAGE_BLOB_NAME='hello.txt' 49 | # GCP Specific Params ---- 50 | GCP_BUCKET_NAME='ship-bucket' -------------------------------------------------------------------------------- /examples/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem 'cloud_project_bootcamp_validation_tool', path: "/workspace/cloud-project-bootcamp-validation-tool" 6 | gem 'dotenv', groups: [:development, :test] 7 | gem 'rake' 8 | gem 'ox' 9 | #gem 'ruby-openai' 10 | gem 'async' 11 | 12 | gem 'zip-zip' 13 | gem 'rubyzip', '~> 2.3.2' -------------------------------------------------------------------------------- /examples/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: /workspace/cloud-project-bootcamp-validation-tool 3 | specs: 4 | cloud_project_bootcamp_validation_tool (1.3.3) 5 | activemodel 6 | aws-sdk-s3 7 | open3 8 | pry 9 | ruby-openai 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | activemodel (7.0.5) 15 | activesupport (= 7.0.5) 16 | activesupport (7.0.5) 17 | concurrent-ruby (~> 1.0, >= 1.0.2) 18 | i18n (>= 1.6, < 2) 19 | minitest (>= 5.1) 20 | tzinfo (~> 2.0) 21 | async (2.6.1) 22 | console (~> 1.10) 23 | fiber-annotation 24 | io-event (~> 1.1) 25 | timers (~> 4.1) 26 | aws-eventstream (1.2.0) 27 | aws-partitions (1.769.0) 28 | aws-sdk-core (3.173.1) 29 | aws-eventstream (~> 1, >= 1.0.2) 30 | aws-partitions (~> 1, >= 1.651.0) 31 | aws-sigv4 (~> 1.5) 32 | jmespath (~> 1, >= 1.6.1) 33 | aws-sdk-kms (1.64.0) 34 | aws-sdk-core (~> 3, >= 3.165.0) 35 | aws-sigv4 (~> 1.1) 36 | aws-sdk-s3 (1.122.0) 37 | aws-sdk-core (~> 3, >= 3.165.0) 38 | aws-sdk-kms (~> 1) 39 | aws-sigv4 (~> 1.4) 40 | aws-sigv4 (1.5.2) 41 | aws-eventstream (~> 1, >= 1.0.2) 42 | coderay (1.1.3) 43 | concurrent-ruby (1.2.2) 44 | console (1.17.2) 45 | fiber-annotation 46 | fiber-local 47 | dotenv (2.8.1) 48 | faraday (2.7.5) 49 | faraday-net_http (>= 2.0, < 3.1) 50 | ruby2_keywords (>= 0.0.4) 51 | faraday-multipart (1.0.4) 52 | multipart-post (~> 2) 53 | faraday-net_http (3.0.2) 54 | fiber-annotation (0.2.0) 55 | fiber-local (1.0.0) 56 | i18n (1.14.1) 57 | concurrent-ruby (~> 1.0) 58 | io-event (1.2.2) 59 | jmespath (1.6.2) 60 | method_source (1.0.0) 61 | minitest (5.18.0) 62 | multipart-post (2.3.0) 63 | open3 (0.1.2) 64 | ox (2.14.16) 65 | pry (0.14.2) 66 | coderay (~> 1.1) 67 | method_source (~> 1.0) 68 | rake (13.0.6) 69 | ruby-openai (4.1.0) 70 | faraday (>= 1) 71 | faraday-multipart (>= 1) 72 | ruby2_keywords (0.0.5) 73 | rubyzip (2.3.2) 74 | timers (4.3.5) 75 | tzinfo (2.0.6) 76 | concurrent-ruby (~> 1.0) 77 | zip-zip (0.3) 78 | rubyzip (>= 1.0.0) 79 | 80 | PLATFORMS 81 | x86_64-linux 82 | 83 | DEPENDENCIES 84 | async 85 | cloud_project_bootcamp_validation_tool! 86 | dotenv 87 | ox 88 | rake 89 | rubyzip (~> 2.3.2) 90 | zip-zip 91 | 92 | BUNDLED WITH 93 | 2.4.9 94 | -------------------------------------------------------------------------------- /examples/aws_2023/dynamic_params.rb: -------------------------------------------------------------------------------- 1 | class Aws2023::DynamicParams 2 | attr_accessor :vpc_id, 3 | :public_subnet_id_1, 4 | :public_subnet_id_2, 5 | :public_subnet_id_3, 6 | :igw_id, 7 | :pipeline_name, 8 | :backend_tg_arn, 9 | :alb_sg_id, 10 | :serv_sg_id, 11 | :static_website_distribution_id, 12 | :static_website_distribution_domain_name, 13 | :assets_distribution_id, 14 | :assets_distribution_domain_name 15 | end -------------------------------------------------------------------------------- /examples/aws_2023/permissioner.rb: -------------------------------------------------------------------------------- 1 | class Aws2023::Permissioner 2 | def self.run(general_params:,specific_params:) 3 | Cpbvt::Payloads::Aws::Policy.new 4 | 5 | unless general_params.valid? 6 | puts general_params.errors.full_messages 7 | raise "failed to pass general params validation" 8 | end 9 | 10 | unless specific_params.valid? 11 | puts specific_params.errors.full_messages 12 | raise "failed to specific params validation" 13 | end 14 | 15 | primary_region = general_params.user_region 16 | self.add primary_region, :ecs_describe_clusters, general_params 17 | self.add primary_region, :ecs_list_services, general_params 18 | self.add primary_region, :ecs_describe_services, general_params, {cluster_name: specific_params.cluster_name, services: [specific_params.ecs_service_name]} 19 | self.add primary_region, :ecs_list_tasks, general_params, {cluster_name: specific_params.cluster_name, family: specific_params.backend_family} 20 | self.add primary_region, :acm_list_certificates, general_params 21 | self.add primary_region, :apigatewayv2_get_apis, general_params 22 | self.add primary_region, :cloudformation_list_stacks, general_params 23 | self.add primary_region, :codebuild_list_projects, general_params 24 | self.add primary_region, :codebuild_list_builds, general_params 25 | self.add primary_region, :codepipeline_list_pipelines, general_params 26 | self.add primary_region, :cognito_idp_list_user_pools, general_params 27 | self.add primary_region, :dynamodb_list_tables, general_params 28 | self.add primary_region, :dynamodbstreams_list_streams, general_params 29 | self.add primary_region, :ec2_describe_vpcs, general_params 30 | self.add primary_region, :ec2_describe_subnets, general_params 31 | self.add primary_region, :ec2_describe_route_tables, general_params 32 | self.add primary_region, :ec2_describe_internet_gateways, general_params 33 | self.add primary_region, :ec2_describe_security_groups, general_params 34 | self.add primary_region, :ec2_describe_route_tables, general_params 35 | self.add primary_region, :ecr_describe_repositories, general_params 36 | self.add primary_region, :ecs_list_task_definitions, general_params 37 | self.add primary_region, :elbv2_describe_load_balancers, general_params 38 | self.add primary_region, :elbv2_describe_target_groups, general_params 39 | self.add primary_region, :lambda_list_functions, general_params 40 | self.add primary_region, :lambda_list_layers, general_params 41 | self.add primary_region, :rds_describe_db_instances, general_params 42 | self.add primary_region, :rds_describe_db_subnet_groups, general_params 43 | self.add primary_region, :rds_describe_db_snapshots, general_params 44 | self.add 'global', :route53_list_hosted_zones, general_params 45 | self.add primary_region, :servicediscovery_list_services, general_params 46 | self.add primary_region, :servicediscovery_list_namespaces, general_params 47 | self.add 'us-east-1', :acm_list_certificates, general_params 48 | self.add 'global', :cloudfront_list_distributions, general_params 49 | self.add 'global', :s3api_list_buckets, general_params 50 | 51 | self.add primary_region, :ecs_describe_tasks, general_params, {cluster_name: specific_params.cluster_name} 52 | 53 | 54 | aliases = [ 55 | "#{specific_params.naked_domain_name}", 56 | "assets.#{specific_params.naked_domain_name}" 57 | ] 58 | self.add 'global', :cloudfront_get_distribution, general_params, {aliases: aliases} 59 | self.add 'global', :cloudfront_list_invalidations, general_params, {aliases: aliases} 60 | 61 | bucket_names = [ 62 | specific_params.raw_assets_bucket_name, 63 | specific_params.naked_domain_name, 64 | "www.#{specific_params.naked_domain_name}", 65 | "assets.#{specific_params.naked_domain_name}" 66 | ] 67 | 68 | # we need this to see lambda get policy data in bucket notification configuration 69 | self.add 'global', :lambda_get_policy, general_params 70 | 71 | self.add 'global', :s3api_get_bucket_notification_configuration, general_params,{bucket_names: bucket_names} 72 | self.add 'global', :s3api_get_bucket_policy, general_params, {bucket_names: bucket_names} 73 | self.add 'global', :s3api_get_bucket_cors, general_params, {bucket_names: bucket_names} 74 | self.add 'global', :s3api_get_bucket_website, general_params, {bucket_names: bucket_names} 75 | self.add 'global', :s3api_get_public_access_block, general_params, {bucket_names: bucket_names} 76 | 77 | self.add primary_region, :elbv2_describe_target_health, general_params 78 | 79 | self.add primary_region, :codebuild_batch_get_projects, general_params 80 | self.add primary_region, :acm_describe_certificate, general_params 81 | self.add primary_region, :apigatewayv2_get_authorizers, general_params 82 | self.add primary_region, :apigatewayv2_get_integrations, general_params 83 | self.add primary_region, :cloudformation_list_stack_resources, general_params 84 | self.add primary_region, :codepipeline_get_pipeline, general_params 85 | self.add primary_region, :cognito_idp_describe_user_pool, general_params 86 | self.add primary_region, :cognito_idp_list_user_pool_clients, general_params 87 | self.add primary_region, :cognito_idp_list_users, general_params 88 | self.add primary_region, :dynamodb_describe_table, general_params 89 | self.add primary_region, :dynamodbstreams_describe_stream, general_params 90 | self.add primary_region, :ecr_describe_images, general_params 91 | self.add primary_region, :elbv2_describe_listeners, general_params 92 | self.add primary_region, :elbv2_describe_load_balancer_attributes, general_params 93 | self.add primary_region, :elbv2_describe_target_group_attributes, general_params 94 | self.add primary_region, :lambda_get_function, general_params 95 | self.add 'global', :route53_get_hosted_zone, general_params 96 | self.add 'global', :route53_list_resource_record_sets, general_params 97 | 98 | 99 | 100 | Cpbvt::Payloads::Aws::Policy.generate! general_params 101 | end 102 | 103 | def self.add region, command, general_params, specific_params={} 104 | Cpbvt::Payloads::Aws::Policy.add region, command, general_params, specific_params 105 | end 106 | end -------------------------------------------------------------------------------- /examples/aws_2023/specific_params.rb: -------------------------------------------------------------------------------- 1 | require 'active_model' 2 | class Aws2023::SpecificParams 3 | include ActiveModel::Validations 4 | attr_accessor :naked_domain_name, 5 | :github_full_repo_name, 6 | :cluster_name, 7 | :backend_family, 8 | :ecr_repo_name, 9 | :ecs_service_name, 10 | :cognito_user_pool_name, 11 | :apigateway_api_id, 12 | :raw_assets_bucket_name, 13 | :cfn_stack_name_machineuser, 14 | :cfn_stack_name_backend, 15 | :cfn_stack_name_sync, 16 | :cfn_stack_name_frontend, 17 | :cfn_stack_name_cicd, 18 | :cfn_stack_name_db, 19 | :cfn_stack_name_ddb, 20 | :cfn_stack_name_cluster, 21 | :cfn_stack_name_networking, 22 | :cfn_stack_name_cdk 23 | 24 | 25 | validates :naked_domain_name, presence: true#, format: { with: /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix } 26 | validates :github_full_repo_name, presence: true 27 | validates :cluster_name, presence: true 28 | validates :backend_family, presence: true 29 | validates :ecr_repo_name, presence: true 30 | validates :ecs_service_name, presence: true 31 | validates :cognito_user_pool_name, presence: true 32 | validates :apigateway_api_id, presence: true 33 | validates :raw_assets_bucket_name, presence: true 34 | validates :cfn_stack_name_machineuser, presence: true 35 | validates :cfn_stack_name_backend, presence: true 36 | validates :cfn_stack_name_sync, presence: true 37 | validates :cfn_stack_name_frontend, presence: true 38 | validates :cfn_stack_name_cicd, presence: true 39 | validates :cfn_stack_name_db, presence: true 40 | validates :cfn_stack_name_ddb, presence: true 41 | validates :cfn_stack_name_cluster, presence: true 42 | validates :cfn_stack_name_networking, presence: true 43 | validates :cfn_stack_name_cdk, presence: true 44 | 45 | def initialize( 46 | naked_domain_name:, 47 | github_full_repo_name:, 48 | cluster_name:, 49 | backend_family:, 50 | ecr_repo_name:, 51 | ecs_service_name:, 52 | cognito_user_pool_name:, 53 | apigateway_api_id:, 54 | raw_assets_bucket_name:, 55 | cfn_stack_name_machineuser:, 56 | cfn_stack_name_backend:, 57 | cfn_stack_name_sync:, 58 | cfn_stack_name_frontend:, 59 | cfn_stack_name_cicd:, 60 | cfn_stack_name_db:, 61 | cfn_stack_name_ddb:, 62 | cfn_stack_name_cluster:, 63 | cfn_stack_name_networking:, 64 | cfn_stack_name_cdk: 65 | ) 66 | 67 | @naked_domain_name = naked_domain_name 68 | @github_full_repo_name = github_full_repo_name 69 | @cluster_name = cluster_name 70 | @backend_family = backend_family 71 | @ecs_service_name = ecs_service_name 72 | @ecr_repo_name = ecr_repo_name 73 | @cognito_user_pool_name = cognito_user_pool_name 74 | @apigateway_api_id = apigateway_api_id 75 | @raw_assets_bucket_name = raw_assets_bucket_name 76 | @cfn_stack_name_machineuser = cfn_stack_name_machineuser 77 | @cfn_stack_name_backend = cfn_stack_name_backend 78 | @cfn_stack_name_sync = cfn_stack_name_sync 79 | @cfn_stack_name_frontend = cfn_stack_name_frontend 80 | @cfn_stack_name_cicd = cfn_stack_name_cicd 81 | @cfn_stack_name_db = cfn_stack_name_db 82 | @cfn_stack_name_ddb = cfn_stack_name_ddb 83 | @cfn_stack_name_cluster = cfn_stack_name_cluster 84 | @cfn_stack_name_networking = cfn_stack_name_networking 85 | @cfn_stack_name_cdk = cfn_stack_name_cdk 86 | end 87 | end -------------------------------------------------------------------------------- /examples/aws_2023/state.rb: -------------------------------------------------------------------------------- 1 | class Aws2023::State 2 | attr_accessor :results, 3 | :manifest, 4 | :specific_params, 5 | :vpc_id, 6 | :public_subnet_id_1, 7 | :public_subnet_id_2, 8 | :public_subnet_id_3, 9 | :igw_id, 10 | :pipeline_name, 11 | :backend_tg_arn, 12 | :alb_sg_id, 13 | :serv_sg_id, 14 | :static_website_distribution_id, 15 | :static_website_distribution_domain_name, 16 | :assets_distribution_id, 17 | :assets_distribution_domain_name 18 | 19 | def initialize 20 | @results = {} 21 | @specific_params = nil 22 | end 23 | 24 | # klass: The class that has the validator 25 | # function_name: The function name we will dynamically call 26 | # input_params: pass these values in from the state file as parameters 27 | # output_params: set these values from the returned payload into the statefile 28 | # override_params: pass the param and value but not from the state file 29 | def process( 30 | klass:, 31 | function_name:, 32 | input_params: [], 33 | output_params: [], 34 | override_params: {}, 35 | rule_name: nil 36 | ) 37 | arguments = { 38 | manifest: self.manifest, 39 | specific_params: specific_params 40 | } 41 | 42 | input_params.each do |param| 43 | unless send(param) 44 | puts "failed to run #{klass}.#{function_name} because #{param} was false" 45 | end 46 | arguments[param] = send(param) 47 | end 48 | 49 | override_params.each{|k,v| arguments[k] = v} 50 | 51 | raise "#{klass}: expected '#{function_name}' function to exist" unless klass.respond_to?(function_name.to_sym) 52 | 53 | data = klass.send(function_name, **arguments) 54 | output_params.each do |param| 55 | puts "assigning output param: #{param}: #{data[param]}" 56 | send("#{param}=", data[param]) 57 | end 58 | rule_name ||= function_name 59 | 60 | raise "#{rule_name}: no data returned" if data.nil? 61 | raise "#{rule_name}: no data with result returned" unless data.key?(:result) 62 | @results[rule_name] = data 63 | end 64 | end -------------------------------------------------------------------------------- /examples/aws_2023/validations/authenication.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :authentication do 2 | spec :should_have_cognito_user_pool do |t| 3 | name = t.specific_params.cognito_user_pool_name 4 | pool_id = assert_load('cognito-idp-list-user-pools','UserPools').find('Name',name).returns('Id') 5 | user_pool = assert_load("cognito-idp-describe-user-pool__#{pool_id}").returns('UserPool') 6 | 7 | assert_json(user_pool,'EstimatedNumberOfUsers').expects_gt(0) 8 | 9 | set_pass_message "Found user pool: #{name} with some users in it" 10 | set_fail_message "Failed to find a user pool: #{name} with some users in it" 11 | end 12 | 13 | spec :should_have_trigger_on_post_confirmation do |t| 14 | name = t.specific_params.cognito_user_pool_name 15 | vpc_id = t.dynamic_params.vpc_id 16 | 17 | pool_id = assert_load('cognito-idp-list-user-pools','UserPools').find('Name',name).returns('Id') 18 | user_pool = assert_load("cognito-idp-describe-user-pool__#{pool_id}").returns('UserPool') 19 | 20 | lambda_arn = assert_json(user_pool,'LambdaConfig','PostConfirmation').expects_not_nil.returns(:all) 21 | lambda_name = lambda_arn.split(':').last 22 | 23 | lambda_config = assert_load("lambda-get-function__#{lambda_name}").returns('Configuration') 24 | 25 | assert_json(lambda_config,'Runtime').expects_match('python3') 26 | assert_json(lambda_config,'CodeSize').expects_gt(0) 27 | assert_json(lambda_config,'VpcConfig','VpcId').expects_eq(vpc_id) 28 | 29 | set_pass_message "Found a Congnito post confirmation python lambda in the custom VPC" 30 | set_fail_message "Failed to find a Cognito post confirmation python lambda in the custom VPC" 31 | end 32 | end -------------------------------------------------------------------------------- /examples/aws_2023/validations/cicd.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :cicd do |t| 2 | spec :should_have_a_codepipeline do |t| 3 | pipeline_name = assert_cfn_resource(t.specific_params.cfn_stack_name_cicd,"AWS::CodePipeline::Pipeline").returns('PhysicalResourceId') 4 | pipeline_name2 = assert_load("codepipeline-get-pipeline__#{pipeline_name}",'pipeline').returns('name') 5 | 6 | set_pass_message "Found a pipeline: #{pipeline_name} via a CFN Stack called CrdCicd" 7 | set_fail_message "Failed to find a pipeline via CFN stack called CrdCicd" 8 | 9 | set_state_value :pipeline_name, pipeline_name2 10 | end # self.should_have_a_codepipeline 11 | 12 | spec :should_have_a_source_from_github do |t| 13 | github = t.specific_params.github_full_repo_name 14 | pipeline_name = t.dynamic_params.pipeline_name 15 | pipeline = assert_load("codepipeline-get-pipeline__#{pipeline_name}",'pipeline').returns(:all) 16 | 17 | assert_json(pipeline,'stages').expects_not_nil 18 | 19 | source_codestar_action = nil 20 | pipeline['stages'].each do |stage| 21 | source_codestar_action = 22 | stage['actions'].find do |action| 23 | if action['actionTypeId']['provider'] == 'CodeStarSourceConnection' 24 | source_codestar_action = action 25 | end 26 | end 27 | break if source_codestar_action 28 | end 29 | 30 | assert_not_nil(source_codestar_action) 31 | 32 | assert_json(source_codestar_action,'configuration','BranchName').expects_eq('prod') 33 | assert_json(source_codestar_action,'configuration','FullRepositoryId').expects_eq(github) 34 | 35 | set_pass_message "Found a CodeStar source action for Branch prod for the repo at: #{github}" 36 | set_fail_message "Failed to find a CodeStar source action for Branch prod for the repo at: #{github}" 37 | end 38 | 39 | spec :should_have_a_build_stage do |t| 40 | pipeline_name = t.dynamic_params.pipeline_name 41 | pipeline = assert_load("codepipeline-get-pipeline__#{pipeline_name}",'pipeline').returns(:all) 42 | 43 | assert_json(pipeline,'stages').expects_not_nil 44 | 45 | build_action = nil 46 | pipeline['stages'].each do |stage| 47 | source_codestar_action = 48 | stage['actions'].find do |action| 49 | if action['actionTypeId']['provider'] == 'CodeBuild' 50 | build_action = action 51 | end 52 | end 53 | break if build_action 54 | end 55 | 56 | assert_not_nil(build_action) 57 | 58 | project_name = assert_json(build_action,'configuration').returns('ProjectName') 59 | 60 | project = assert_load("codebuild-batch-get-projects__#{project_name}",'projects').returns(:first) 61 | 62 | tags = assert_json(project,'tags').returns(:all) 63 | 64 | tag = 65 | assert_find(tags) do |assert, tag| 66 | assert.expects_eq(tag,'key','group') 67 | assert.expects_eq(tag,'value','cruddur-cicd') 68 | end.returns(:all) 69 | 70 | assert_not_nil(tag) 71 | 72 | assert_json(project,'source','type').expects_eq('CODEPIPELINE') 73 | assert_json(project,'environment','privilegedMode').expects_true 74 | 75 | set_pass_message "Found a codebuild action within the codepipeline and the codebuild project has privledge mode with tag group:cruddur-cicid" 76 | set_fail_message "Failed to find a codebuild action within the codepipeline and the codebuild project has privledge mode with tag group:cruddur-cicid" 77 | end 78 | 79 | spec :should_have_a_deploy_stage do |t| 80 | cluster_name = t.specific_params.cluster_name 81 | pipeline_name = t.dynamic_params.pipeline_name 82 | pipeline = assert_load("codepipeline-get-pipeline__#{pipeline_name}",'pipeline').returns(:all) 83 | 84 | assert_json(pipeline,'stages').expects_not_nil 85 | 86 | deploy_action = nil 87 | pipeline['stages'].each do |stage| 88 | source_codestar_action = 89 | stage['actions'].find do |action| 90 | if action['actionTypeId']['provider'] == 'ECS' && 91 | action['actionTypeId']['category'] == 'Deploy' 92 | deploy_action = action 93 | end 94 | end 95 | break if deploy_action 96 | end 97 | 98 | assert_not_nil(deploy_action) 99 | 100 | assert_json(deploy_action,'configuration','ClusterName').expects_eq(cluster_name) 101 | assert_json(deploy_action,'configuration','ServiceName').expects_eq('backend-flask') 102 | 103 | set_pass_message "Found a Deploy with ECS for backend-flask service within the CodePipeline stages" 104 | set_fail_message "Failed to find Deploy with ECS for backend-flask service within the CodePipeline stages" 105 | end # def self.should_have_a_deploy_stage 106 | end # class Aws2023::Validations::Cicd -------------------------------------------------------------------------------- /examples/aws_2023/validations/db.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :db do 2 | spec :should_have_public_rds_instance do |t| 3 | vpc_id = t.dynamic_params.vpc_id 4 | 5 | db_id = assert_cfn_resource(t.specific_params.cfn_stack_name_db,"AWS::RDS::DBInstance").returns('PhysicalResourceId') 6 | db = assert_load('rds-describe-db-instances','DBInstances').find('DBInstanceIdentifier',db_id).returns(:all) 7 | 8 | assert_json(db,'PubliclyAccessible').expects_true 9 | assert_json(db,'Engine').expects_eq('postgres') 10 | assert_json(db,'DBSubnetGroup','VpcId').expects_eq(vpc_id) 11 | 12 | set_pass_message "Found the primary database running an RDS instance of postgres" 13 | set_fail_message "Failed to find the primary database running an RDS instance of postgres" 14 | end 15 | 16 | spec :should_have_db_sg do |t| 17 | serv_sg_id = t.dynamic_params.serv_sg_id 18 | 19 | sg_id = assert_cfn_resource(t.specific_params.cfn_stack_name_db,"AWS::EC2::SecurityGroup").returns('PhysicalResourceId') 20 | 21 | sg = assert_load('ec2-describe-security-groups','SecurityGroups').find('GroupId',sg_id).returns(:all) 22 | 23 | rules = assert_json(sg,'IpPermissions').returns(:all) 24 | 25 | serv_sg_rule = 26 | assert_find(rules) do |assert,rule| 27 | assert.expects_eq(rule,'FromPort',5432) 28 | assert.expects_eq(rule,'ToPort',5432) 29 | assert.expects_any?(rule,'UserIdGroupPairs') do |pair| 30 | pair['GroupId'] == serv_sg_id 31 | end 32 | end.returns(:all) 33 | 34 | assert_not_nil(serv_sg_rule) 35 | 36 | set_pass_message "Found a Security Group for the RDS Instance with ingress allowed on port 4567 for the backend-service SG" 37 | set_fail_message "Failed to find a Security Group for the RDS Instance with ingress allowed on port 4567 for the backend-service SG" 38 | end 39 | end -------------------------------------------------------------------------------- /examples/aws_2023/validations/ddb.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :ddb do 2 | spec :should_have_ddb_table do |t| 3 | stack_name_ddb = t.specific_params.cfn_stack_name_ddb 4 | table_name = assert_cfn_resource(stack_name_ddb,"AWS::DynamoDB::Table").returns('PhysicalResourceId') 5 | table = assert_load("dynamodb-describe-table__#{table_name}",'Table').returns(:all) 6 | 7 | key_schema = assert_json(table,'KeySchema').returns(:all) 8 | 9 | pk = 10 | assert_find(key_schema) do |assert,item| 11 | assert.expects_eq(item,'AttributeName','pk') 12 | assert.expects_eq(item,'KeyType','HASH') 13 | end.returns(:all) 14 | 15 | sk = 16 | assert_find(key_schema) do |assert,item| 17 | assert.expects_eq(item,'AttributeName','sk') 18 | assert.expects_eq(item,'KeyType','RANGE') 19 | end.returns(:all) 20 | 21 | assert_not_nil(pk) 22 | assert_not_nil(sk) 23 | 24 | assert_json(table,'TableStatus').expects_eq('ACTIVE') 25 | assert_json(table,'TableSizeBytes').expects_gt(0) 26 | assert_json(table,'ItemCount').expects_gt(0) 27 | 28 | set_pass_message "Found dynamodb table with data in it" 29 | set_fail_message "Failed to find dynamodb table with data in it" 30 | end 31 | 32 | spec :should_have_gsi do |t| 33 | table_name = assert_cfn_resource(t.specific_params.cfn_stack_name_ddb,"AWS::DynamoDB::Table").returns('PhysicalResourceId') 34 | table = assert_load("dynamodb-describe-table__#{table_name}",'Table').returns(:all) 35 | 36 | gsi = assert_json(table,'GlobalSecondaryIndexes').returns(:first) 37 | key_schema = assert_json(gsi,'KeySchema').returns(:all) 38 | 39 | pk = 40 | assert_find(key_schema) do |assert,item| 41 | assert.expects_eq(item,'AttributeName','message_group_uuid') 42 | assert.expects_eq(item,'KeyType','HASH') 43 | end.returns(:all) 44 | 45 | sk = 46 | assert_find(key_schema) do |assert,item| 47 | assert.expects_eq(item,'AttributeName','sk') 48 | assert.expects_eq(item,'KeyType','RANGE') 49 | end.returns(:all) 50 | 51 | assert_not_nil(pk) 52 | assert_not_nil(sk) 53 | 54 | assert_json(gsi,'IndexStatus').expects_eq('ACTIVE') 55 | assert_json(gsi,'IndexSizeBytes').expects_gt(0) 56 | assert_json(gsi,'ItemCount').expects_gt(0) 57 | 58 | set_pass_message "Found dynamodb table GSI with data in it" 59 | set_fail_message "Failed to find dynamodb table GSI with data in it" 60 | end 61 | 62 | spec :should_have_ddb_stream do |t| 63 | table_name = assert_cfn_resource(t.specific_params.cfn_stack_name_ddb,"AWS::DynamoDB::Table").returns('PhysicalResourceId') 64 | table = assert_load("dynamodb-describe-table__#{table_name}",'Table').returns(:all) 65 | 66 | label = assert_json(table,'LatestStreamLabel').returns(:all) 67 | 68 | desc = assert_load("dynamodbstreams-describe-stream__#{label}").returns('StreamDescription') 69 | 70 | assert_json(desc,'StreamStatus').expects_eq('ENABLED') 71 | assert_json(desc,'StreamViewType').expects_eq('NEW_IMAGE') 72 | 73 | set_pass_message "Found dynamodb stream with NEW_IMAGE" 74 | set_fail_message "Failed to find dynamodb stream with NEW_IMAGE" 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /examples/aws_2023/validations/iac.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :iac do 2 | spec "should_have_stack_machineuser".to_sym do |t| 3 | stack_name = t.specific_params.cfn_stack_name_machineuser 4 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 5 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 6 | assert_include?(statuses,nil,status) 7 | end 8 | 9 | spec "should_have_stack_backend".to_sym do |t| 10 | stack_name = t.specific_params.cfn_stack_name_backend 11 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 12 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 13 | assert_include?(statuses,nil,status) 14 | end 15 | 16 | spec "should_have_stack_sync".to_sym do |t| 17 | stack_name = t.specific_params.cfn_stack_name_sync 18 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 19 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 20 | assert_include?(statuses,nil,status) 21 | end 22 | 23 | spec "should_have_stack_frontend".to_sym do |t| 24 | stack_name = t.specific_params.cfn_stack_name_frontend 25 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 26 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 27 | assert_include?(statuses,nil,status) 28 | end 29 | 30 | spec "should_have_stack_cicd".to_sym do |t| 31 | stack_name = t.specific_params.cfn_stack_name_cicd 32 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 33 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 34 | assert_include?(statuses,nil,status) 35 | end 36 | 37 | spec "should_have_stack_db".to_sym do |t| 38 | stack_name = t.specific_params.cfn_stack_name_db 39 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 40 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 41 | assert_include?(statuses,nil,status) 42 | end 43 | 44 | spec "should_have_stack_ddb".to_sym do |t| 45 | stack_name = t.specific_params.cfn_stack_name_ddb 46 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 47 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 48 | assert_include?(statuses,nil,status) 49 | end 50 | 51 | spec "should_have_stack_cluster".to_sym do |t| 52 | stack_name = t.specific_params.cfn_stack_name_cluster 53 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 54 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 55 | assert_include?(statuses,nil,status) 56 | end 57 | 58 | spec "should_have_stack_networking".to_sym do |t| 59 | stack_name = t.specific_params.cfn_stack_name_networking 60 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 61 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 62 | assert_include?(statuses,nil,status) 63 | end 64 | 65 | spec "should_have_stack_serverless_cdk".to_sym do |t| 66 | stack_name = t.specific_params.cfn_stack_name_cdk 67 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 68 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 69 | assert_include?(statuses,nil,status) 70 | end 71 | 72 | spec "should_have_stack_cdk_toolkit".to_sym do |t| 73 | stack_name = 'CDKToolkit' 74 | status = assert_load('cloudformation-list-stacks','StackSummaries').find('StackName',stack_name).returns('StackStatus') 75 | statuses = %w{CREATE_COMPLETE UPDATE_COMPLETE} 76 | assert_include?(statuses,nil,status) 77 | end 78 | end -------------------------------------------------------------------------------- /examples/aws_2023/validations/networking.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :networking do |t| 2 | spec :should_have_custom_vpc do |t| 3 | vpcs = assert_load('ec2_describe_vpcs','Vpcs').returns(:all) 4 | 5 | vpc_id = 6 | assert_find(vpcs) do |assert, vpc| 7 | assert.expects_false(vpc,'IsDefault') 8 | assert.expects_eq(vpc,'State','available') 9 | assert.expects_any?(vpc,'Tags', label: "Tags:group:cruddur-networking") do |tag| 10 | tag['Key'] == 'group' && 11 | tag['Value'] == 'cruddur-networking' 12 | end 13 | assert.expects_any?(vpc,'Tags', label: "aws:cloudformation:stack-name") do |tag| 14 | tag['Key'] == 'aws:cloudformation:stack-name' 15 | end 16 | end.returns('VpcId') 17 | 18 | set_pass_message "Found multiple custom VPCs [non default VPC] that are avaliable. Uncertain which is the correct one." 19 | set_fail_message "Failed to find any custom VPC [non default VPC] that is avaliable tagged with group:cruddur-networking" 20 | 21 | vpc_id = false if vpc_id.nil? 22 | 23 | set_state_value :vpc_id, vpc_id 24 | end 25 | 26 | spec :should_have_three_public_subnets do |t| 27 | data = assert_load('ec2_describe_subnets','Subnets').returns(:all) 28 | 29 | subnets = 30 | assert_select(data) do |assert, subnet| 31 | assert.expects_any?(subnet,'Tags', label: "Tags:group:cruddur-networking") do |tag| 32 | tag['Key'] == 'group' && 33 | tag['Value'] == 'cruddur-networking' 34 | end 35 | assert.expects_eq(subnet,'State','available') 36 | assert.expects_true(subnet,'MapPublicIpOnLaunch') 37 | end.returns(:all) 38 | end 39 | 40 | spec :should_have_an_igw do |t| 41 | vpc_id = t.dynamic_params.vpc_id 42 | 43 | igws = assert_load('ec2_describe_internet_gateways','InternetGateways').returns(:all) 44 | 45 | igw_id = 46 | assert_find(igws) do |assert, igw| 47 | assert.expects_any?(igw,'Tags', label: "Tags:group:cruddur-networking") do |tag| 48 | tag['Key'] == 'group' && 49 | tag['Value'] == 'cruddur-networking' 50 | end 51 | assert.expects_any?(igw,'Attachments',label: "State:available,vpcid:#{vpc_id}") do |attachment| 52 | attachment['State'] == 'available' && 53 | attachment['VpcId'] == vpc_id 54 | end 55 | end.returns('InternetGatewayId') #assert_find 56 | 57 | set_pass_message "Found an IGW attached to the vpc: #{vpc_id} tagged with group:cruddur-networking" 58 | set_fail_message "Failed to find an IGW attached to the vpc: #{vpc_id} tagged with group:cruddur-networking" 59 | 60 | igw_id = false if igw_id.nil? 61 | 62 | set_state_value :igw_id, igw_id 63 | end # spec 64 | 65 | spec :should_have_a_route_to_internet do |t| 66 | igw_id = t.dynamic_params.igw_id 67 | vpc_id = t.dynamic_params.vpc_id 68 | 69 | route_tables = assert_load('ec2_describe_route_tables','RouteTables').returns(:all) 70 | 71 | route_table = 72 | assert_find(route_tables) do |assert, route_table| 73 | assert.expects_any?(route_table,'Tags', label: "Tags:group:cruddur-networking") do |tag| 74 | tag['Key'] == 'group' && 75 | tag['Value'] == 'cruddur-networking' 76 | end 77 | assert.expects_any?(route_table,'Routes',label: "GatewayId:#{igw_id},DestinationCidrBlock:0.0.0.0/0") do |route| 78 | route['GatewayId'] == igw_id && 79 | route['DestinationCidrBlock'] == '0.0.0.0/0' 80 | end 81 | assert.expects_eq(route_table,'VpcId',vpc_id) 82 | end.returns(:all) #assert_find 83 | 84 | assert_not_nil(route_table, label: 'Route table not nil') 85 | 86 | set_pass_message "Found a route table for vpc: #{vpc_id} that routes out to the internet" 87 | set_fail_message "Failed to find a route table for vpc: #{vpc_id} that routes out to the internet" 88 | end 89 | end -------------------------------------------------------------------------------- /examples/aws_2023/validations/serverless.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :serverless do 2 | spec :should_have_key_upload_route do |t| 3 | api_id = t.specific_params.apigateway_api_id 4 | bucket_name = t.specific_params.raw_assets_bucket_name 5 | 6 | route = assert_load("apigatewayv2-get-routes__#{api_id}",'Items').find('RouteKey',"POST /avatars/key_upload").returns(:all) 7 | 8 | target_arn = assert_json(route,'Target').returns(:all) 9 | 10 | integration_id = target_arn.split('/').last 11 | 12 | integration = assert_load("apigatewayv2-get-integrations__#{api_id}",'Items') 13 | .find('IntegrationId',integration_id).returns(:all) 14 | 15 | lambda_arn = assert_json(integration,'IntegrationUri').returns(:all) 16 | lambda_name = lambda_arn.split(':').last 17 | 18 | lambda_config = assert_load("lambda-get-function__#{lambda_name}",'Configuration').returns(:all) 19 | 20 | assert_json(lambda_config,'Runtime').expects_match('ruby') 21 | assert_json(lambda_config,'CodeSize').expects_gt(0) 22 | assert_json(integration,'ConnectionType').expects_eq('INTERNET') 23 | assert_json(integration,'IntegrationMethod').expects_eq('POST') 24 | assert_json(integration,'IntegrationType').expects_eq('AWS_PROXY') 25 | assert_json(route,'AuthorizationType').expects_eq('CUSTOM') 26 | assert_json(lambda_config,'Environment','Variables','UPLOADS_BUCKET_NAME').expects_eq(bucket_name) 27 | 28 | set_pass_message "Found API Gateway route ruby lambda with custom authorization for POST /avatars/key_upload" 29 | set_fail_message "Failed to find API Gateway route ruby lambda with custom authorization for POST /avatars/key_upload" 30 | end 31 | 32 | spec :should_have_proxy_route do |t| 33 | api_id = specific_params.apigateway_api_id 34 | 35 | route = assert_load("apigatewayv2-get-routes__#{api_id}",'Items').find('RouteKey',"OPTIONS /{proxy+}").returns(:all) 36 | 37 | target_arn = assert_json(route,'Target').returns(:all) 38 | 39 | assert_not_nil(target_arn) 40 | integration_id = target_arn.split('/').last 41 | 42 | integration = assert_load("apigatewayv2-get-integrations__#{api_id}",'Items') 43 | .find('IntegrationId',integration_id).returns(:all) 44 | 45 | lambda_arn = assert_json(integration,'IntegrationUri').returns(:all) 46 | lambda_name = lambda_arn.split(':').last 47 | 48 | lambda_config = assert_load("lambda-get-function__#{lambda_name}",'Configuration').returns(:all) 49 | 50 | assert_json(route,'AuthorizationType').expects_eq('NONE') 51 | assert_json(lambda_config,'CodeSize').expects_gt(0) 52 | assert_json(integration,'ConnectionType').expects_eq('INTERNET') 53 | assert_json(integration,'IntegrationMethod').expects_eq('POST') 54 | assert_json(integration,'IntegrationType').expects_eq('AWS_PROXY') 55 | assert_json(route,'AuthorizationType').expects_eq('NONE') 56 | 57 | set_pass_message "Found API Gateway route OPTIONS /{proxy+" 58 | set_fail_message "Failed to find API Gateway route OPTIONS /{proxy+" 59 | end 60 | 61 | spec :should_have_s3_bucket_with_event_notification do |t| 62 | bucket_name = specific_params.raw_assets_bucket_name 63 | naked_domain_name = t.specific_params.naked_domain_name 64 | 65 | event = assert_load("s3api-get-bucket-notification-configuration__#{bucket_name}",'LambdaFunctionConfigurations').returns(:first) 66 | 67 | lambda_arn = assert_json(event,'LambdaFunctionArn').returns(:all) 68 | assert_not_nil(lambda_arn) 69 | lambda_name = lambda_arn.split(':').last 70 | lambda_config = assert_load("lambda-get-function__#{lambda_name}",'Configuration').returns(:all) 71 | 72 | dest_bucket = assert_json(lambda_config,'Environment','Variables','DEST_BUCKET_NAME').returns(:all) 73 | 74 | assert_json(lambda_config,'Runtime').expects_match(/nodejs/) 75 | assert_eq(dest_bucket,"assets.#{naked_domain_name}") 76 | assert_json(lambda_config,'CodeSize').expects_gt(0) 77 | 78 | event = assert_json(event,'Events').returns(:first) 79 | assert_eq(event,'s3:ObjectCreated:Put') 80 | 81 | set_fail_message "Found raw assets bucket that has an event notification on object put to assets.#{naked_domain_name}" 82 | set_pass_message "Failed to raw assets bucket that has an event notification on object put to assets.#{naked_domain_name}" 83 | end 84 | 85 | spec :should_block_public_access_for_assets_bucket do |t| 86 | naked_domain_name = t.specific_params.naked_domain_name 87 | 88 | access = assert_load("s3api-get-public-access-block__assets.#{naked_domain_name}",'PublicAccessBlockConfiguration').returns(:all) 89 | 90 | assert_json(access,'BlockPublicPolicy').expects_true 91 | assert_json(access,'RestrictPublicBuckets').expects_true 92 | 93 | set_pass_message "Found s3 static website for assets.#{naked_domain_name} to disallow bucket policies" 94 | set_fail_message "Failed to find ss3 static website for assets.#{naked_domain_name} to disallow bucket policies" 95 | end 96 | 97 | spec :should_have_a_cloudfront_distrubition_to_assets do |t| 98 | assets_domain_name = "assets.#{specific_params.naked_domain_name}" 99 | 100 | items = assert_load('cloudfront-list-distributions','DistributionList').returns('Items') 101 | 102 | distribution = 103 | assert_find(items) do |assert,distribution| 104 | aliases = distribution['Aliases'] 105 | assert.expects_eq(aliases,'Quantity',1) 106 | item = aliases['Items'].first 107 | assert.expects_eq(item,assets_domain_name) 108 | end.returns(:all) 109 | 110 | assert_not_nil(distribution) 111 | 112 | domain = assert_json(distribution,'Origins','Items').returns(:first) 113 | domain_name = assert_json(domain,'DomainName').returns(:all) 114 | 115 | assert_json(distribution,'Status').expects_eq('Deployed') 116 | assert_json(distribution,'Origins','Quantity').expects_eq(1) 117 | assert_start_with(domain_name,"#{assets_domain_name}.s3") 118 | assert_end_with(domain_name,".amazonaws.com") 119 | 120 | dist_id = assert_json(distribution,'Id').returns(:all) 121 | dist_domain_name = assert_json(distribution,'DomainName').returns(:all) 122 | 123 | set_state_value :assets_distribution_id, dist_id 124 | set_state_value :assets_distribution_domain_name, dist_domain_name 125 | 126 | set_pass_message "Found CloudFront distrubution with origin to S3 bucket: #{assets_domain_name}.s3.amazonaws.com" 127 | set_fail_message "Failed to find CloudFront distrubution with origin to S3 bucket: #{assets_domain_name}.s3.amazonaws.com" 128 | end 129 | 130 | spec :should_have_route53_to_distribution_for_assets do |t| 131 | naked_domain_name = t.specific_params.naked_domain_name 132 | assets_domain_name = "assets.#{t.specific_params.naked_domain_name}" 133 | dist_domain_name = t.dynamic_params.assets_distribution_domain_name 134 | zone_arn = assert_load('route53-list-hosted-zones','HostedZones').find('Name',"#{naked_domain_name}.").returns('Id') 135 | 136 | assert_not_nil(zone_arn) 137 | zone_id = zone_arn.split("/").last 138 | 139 | record_sets = assert_load("route53-list-resource-record-sets__#{zone_id}",'ResourceRecordSets').returns(:all) 140 | 141 | record = 142 | assert_find(record_sets) do |assert,record| 143 | assert.expects_eq(record,'Name',"#{assets_domain_name}.") 144 | assert.expects_eq(record,'Type',"A") 145 | end.returns(:all) 146 | 147 | assert_not_nil(record) 148 | assert_json(record,'AliasTarget','DNSName').expects_eq("#{dist_domain_name}.") 149 | 150 | set_fail_message "Found route53 assets domain and pointing to the cloudfront distribution for assets" 151 | set_pass_message "Failed to find a route53 assets domain and pointing to the cloudfront distribution for assets" 152 | end 153 | end -------------------------------------------------------------------------------- /examples/aws_2023/validator.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | class Aws2023::Validator2 4 | 5 | def self.run( 6 | validations_path:, 7 | general_params:, 8 | specific_params:, 9 | dynamic_params:, 10 | load_order: [] 11 | ) 12 | unless general_params.valid? 13 | puts general_params.errors.full_messages 14 | raise "failed to pass general params validation" 15 | end 16 | 17 | unless specific_params.valid? 18 | puts specific_params.errors.full_messages 19 | raise "failed to specific params validation" 20 | end 21 | 22 | state = Aws2023::State.new 23 | 24 | manifest = Cpbvt::Manifest.new( 25 | user_uuid: general_params.user_uuid, 26 | run_uuid: general_params.run_uuid, 27 | output_path: general_params.output_path, 28 | project_scope: general_params.project_scope, 29 | payloads_bucket: general_params.payloads_bucket 30 | ) 31 | manifest.load_from_file! 32 | manifest.pull! 33 | 34 | Cpbvt::Tester::Runner.run!( 35 | validations_path: "/workspace/cloud-project-bootcamp-validation-tool/examples/aws_2023/validations2", 36 | load_order: load_order, 37 | manifest: manifest, 38 | general_params: general_params, 39 | specific_params: specific_params, 40 | dynamic_params: dynamic_params 41 | ) 42 | end # def self.run 43 | end # class -------------------------------------------------------------------------------- /examples/azure/dynamic_params.rb: -------------------------------------------------------------------------------- 1 | class Azure::DynamicParams 2 | end -------------------------------------------------------------------------------- /examples/azure/puller.rb: -------------------------------------------------------------------------------- 1 | require 'async' 2 | 3 | class Azure::Puller 4 | def self.run(general_params:,specific_params:) 5 | unless general_params.valid? 6 | puts general_params.errors.full_messages 7 | raise "failed to pass general params validation" 8 | end 9 | 10 | unless specific_params.valid? 11 | puts specific_params.errors.full_messages 12 | raise "failed to specific params validation" 13 | end 14 | 15 | manifest = Cpbvt::Manifest.new( 16 | user_uuid: general_params.user_uuid, 17 | run_uuid: general_params.run_uuid, 18 | project_scope: general_params.project_scope, 19 | output_path: general_params.output_path, 20 | payloads_bucket: general_params.payloads_bucket 21 | ) 22 | 23 | creds = Cpbvt::Payloads::Azure::Command.login( 24 | general_params.azure_client_id, 25 | general_params.azure_client_secret, 26 | general_params.azure_tenant_id 27 | ) 28 | account_name = specific_params.storage_account_name 29 | container_name = specific_params.storage_container_name 30 | blob_name = specific_params.storage_blob_name 31 | xblob_name = File.basename(blob_name, File.extname(blob_name)) 32 | Async do |task| 33 | self.pull_async task, :az_storage_account_list, manifest, general_params 34 | 35 | self.pull_async(task, 36 | :az_storage_container_list, 37 | manifest, 38 | general_params.to_h.merge({ 39 | filename: "#{:az_storage_container_list.to_s.gsub('_','-')}__#{account_name}.json", 40 | }), 41 | {account_name: account_name } 42 | ) 43 | self.pull_async(task, 44 | :az_storage_blob_exists, 45 | manifest, 46 | general_params.to_h.merge({ 47 | filename: "#{:az_storage_blob_exists.to_s.gsub('_','-')}__#{account_name}__#{container_name}__#{xblob_name}.json", 48 | }), 49 | {account_name: account_name, container_name: specific_params.storage_container_name, blob_name: blob_name } 50 | ) 51 | end 52 | 53 | manifest.write_file! 54 | manifest.archive! 55 | 56 | Cpbvt::Uploader.run( 57 | file_path: manifest.output_file, 58 | object_key: manifest.object_key, 59 | aws_region: general_params.region, 60 | aws_access_key_id: general_params.aws_access_key_id, 61 | aws_secret_access_key: general_params.aws_secret_access_key, 62 | payloads_bucket: general_params.payloads_bucket 63 | ) 64 | end 65 | 66 | def self.pull_async(task,command,manifest,general_params,specific_params={}) 67 | task.async do 68 | self.pull(command,manifest,general_params,specific_params) 69 | end 70 | end 71 | 72 | def self.pull(command,manifest,general_params,specific_params={}) 73 | general_params = general_params.to_h unless general_params.is_a?(Hash) 74 | result = Cpbvt::Payloads::Azure::Runner.run( 75 | command.to_s, 76 | general_params, 77 | specific_params 78 | ) 79 | manifest.add_payload result[:id], result 80 | end 81 | end -------------------------------------------------------------------------------- /examples/azure/specific_params.rb: -------------------------------------------------------------------------------- 1 | require 'active_model' 2 | class Azure::SpecificParams 3 | include ActiveModel::Validations 4 | 5 | attr_accessor :storage_account_name, 6 | :storage_container_name, 7 | :storage_blob_name 8 | 9 | validates :storage_account_name, presence: true 10 | validates :storage_container_name, presence: true 11 | validates :storage_blob_name, presence: true 12 | 13 | def initialize( 14 | storage_account_name:, 15 | storage_container_name:, 16 | storage_blob_name:) 17 | 18 | @storage_account_name = storage_account_name 19 | @storage_container_name = storage_container_name 20 | @storage_blob_name = storage_blob_name 21 | end 22 | end -------------------------------------------------------------------------------- /examples/azure/state.rb: -------------------------------------------------------------------------------- 1 | class Azure::State 2 | attr_accessor :results, 3 | :manifest, 4 | :specific_params 5 | 6 | def initialize 7 | @results = {} 8 | @specific_params = nil 9 | end 10 | 11 | # klass: The class that has the validator 12 | # function_name: The function name we will dynamically call 13 | # input_params: pass these values in from the state file as parameters 14 | # output_params: set these values from the returned payload into the statefile 15 | # override_params: pass the param and value but not from the state file 16 | def process( 17 | klass:, 18 | function_name:, 19 | input_params: [], 20 | output_params: [], 21 | override_params: {}, 22 | rule_name: nil 23 | ) 24 | arguments = { 25 | manifest: self.manifest, 26 | specific_params: specific_params 27 | } 28 | 29 | input_params.each do |param| 30 | unless send(param) 31 | puts "failed to run #{klass}.#{function_name} because #{param} was false" 32 | end 33 | arguments[param] = send(param) 34 | end 35 | 36 | override_params.each{|k,v| arguments[k] = v} 37 | 38 | raise "#{klass}: expected '#{function_name}' function to exist" unless klass.respond_to?(function_name.to_sym) 39 | 40 | data = klass.send(function_name, **arguments) 41 | output_params.each do |param| 42 | puts "assigning output param: #{param}: #{data[param]}" 43 | send("#{param}=", data[param]) 44 | end 45 | rule_name ||= function_name 46 | 47 | raise "#{rule_name}: no data returned" if data.nil? 48 | raise "#{rule_name}: no data with result returned" unless data.key?(:result) 49 | @results[rule_name] = data 50 | end # def process 51 | end -------------------------------------------------------------------------------- /examples/azure/validations/example.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :example do 2 | spec :should_have_account do |t| 3 | account_name = t.specific_params.storage_account_name 4 | account = assert_load("az-storage-account-list").find("name",account_name).returns(:all) 5 | assert_not_nil(account) 6 | set_pass_message "Found Azure Storage Account named #{account_name}" 7 | set_fail_message "Failed to Azure Storage Account named #{account_name}" 8 | end 9 | 10 | spec :should_have_container do |t| 11 | account_name = t.specific_params.storage_account_name 12 | container_name = t.specific_params.storage_container_name 13 | account = assert_load("az-storage-container-list__#{account_name}").find("name",container_name).returns(:all) 14 | 15 | assert_not_nil(account) 16 | set_pass_message "Found Azure Storage Account container named #{container_name}" 17 | set_fail_message "Failed to Azure Storage Account container named #{container_name}" 18 | end 19 | 20 | spec :should_have_blob do |t| 21 | account_name = t.specific_params.storage_account_name 22 | container_name = t.specific_params.storage_container_name 23 | blob_name = t.specific_params.storage_blob_name 24 | xblob_name = File.basename(blob_name, File.extname(blob_name)) 25 | blob_result = assert_load("az-storage-blob-exists__#{account_name}__#{container_name}__#{xblob_name}").returns(:all) 26 | 27 | 28 | assert_eq(blob_result,'exists', true) 29 | 30 | set_pass_message "Found blob in container named #{blob_name}" 31 | set_fail_message "Failed blob in container named #{blob_name}" 32 | end 33 | end -------------------------------------------------------------------------------- /examples/azure/validator.rb: -------------------------------------------------------------------------------- 1 | class Azure::Validator 2 | def self.run( 3 | validations_path:, 4 | general_params:, 5 | specific_params:, 6 | dynamic_params:, 7 | load_order: [] 8 | ) 9 | unless general_params.valid? 10 | puts general_params.errors.full_messages 11 | raise "failed to pass general params validation" 12 | end 13 | 14 | unless specific_params.valid? 15 | puts specific_params.errors.full_messages 16 | raise "failed to specific params validation" 17 | end 18 | 19 | state = Azure::State.new 20 | 21 | manifest = Cpbvt::Manifest.new( 22 | user_uuid: general_params.user_uuid, 23 | run_uuid: general_params.run_uuid, 24 | output_path: general_params.output_path, 25 | project_scope: general_params.project_scope, 26 | payloads_bucket: general_params.payloads_bucket 27 | ) 28 | manifest.load_from_file! 29 | manifest.pull! 30 | 31 | Cpbvt::Tester::Runner.run!( 32 | validations_path: validations_path, 33 | load_order: load_order, 34 | manifest: manifest, 35 | general_params: general_params, 36 | specific_params: specific_params, 37 | dynamic_params: dynamic_params 38 | ) 39 | end # def self.run 40 | end -------------------------------------------------------------------------------- /examples/bin/deploy: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -e # stop the execution of the script if it fails 3 | 4 | CFN_PATH="/workspace/cloud-project-bootcamp-validation-tool/examples/output/aws-bootcamp-2023/$USER_UUID/cross-account-role.yaml" 5 | 6 | STACK_NAME="CPBVT-AWS2023" 7 | 8 | aws cloudformation deploy \ 9 | --stack-name $STACK_NAME \ 10 | --template-file "$CFN_PATH" \ 11 | --no-execute-changeset \ 12 | --parameter-overrides SourceAccountId=$SOURCE_AWS_ACCOUNT_ID ExternalId=$EXTERNAL_ID \ 13 | --capabilities CAPABILITY_NAMED_IAM -------------------------------------------------------------------------------- /examples/gcp/dynamic_params.rb: -------------------------------------------------------------------------------- 1 | class Gcp::DynamicParams 2 | end -------------------------------------------------------------------------------- /examples/gcp/puller.rb: -------------------------------------------------------------------------------- 1 | require 'async' 2 | 3 | class Gcp::Puller 4 | def self.run(general_params:,specific_params:) 5 | unless general_params.valid? 6 | puts general_params.errors.full_messages 7 | raise "failed to pass general params validation" 8 | end 9 | 10 | unless specific_params.valid? 11 | puts specific_params.errors.full_messages 12 | raise "failed to specific params validation" 13 | end 14 | 15 | manifest = Cpbvt::Manifest.new( 16 | user_uuid: general_params.user_uuid, 17 | run_uuid: general_params.run_uuid, 18 | project_scope: general_params.project_scope, 19 | output_path: general_params.output_path, 20 | payloads_bucket: general_params.payloads_bucket 21 | ) 22 | 23 | creds = Cpbvt::Payloads::Gcp::Command.login general_params.gcp_key_file 24 | 25 | Async do |task| 26 | self.pull_async task, :gcloud_storage_buckets_list, manifest, general_params 27 | 28 | bucket_name = specific_params.gcp_bucket_name 29 | self.pull_async(task, 30 | :gcloud_storage_objects_describe, 31 | manifest, 32 | general_params.to_h.merge({ 33 | filename: "#{:gcloud_storage_objects_describe.to_s.gsub('_','-')}__#{bucket_name}.json", 34 | }), 35 | {bucket_name: bucket_name, object_name: 'ships.csv' } 36 | ) 37 | 38 | 39 | end 40 | 41 | manifest.write_file! 42 | manifest.archive! 43 | 44 | Cpbvt::Uploader.run( 45 | file_path: manifest.output_file, 46 | object_key: manifest.object_key, 47 | aws_region: general_params.region, 48 | aws_access_key_id: general_params.aws_access_key_id, 49 | aws_secret_access_key: general_params.aws_secret_access_key, 50 | payloads_bucket: general_params.payloads_bucket 51 | ) 52 | end 53 | 54 | def self.pull_async(task,command,manifest,general_params,specific_params={}) 55 | task.async do 56 | self.pull(command,manifest,general_params,specific_params) 57 | end 58 | end 59 | 60 | def self.pull(command,manifest,general_params,specific_params={}) 61 | general_params = general_params.to_h unless general_params.is_a?(Hash) 62 | result = Cpbvt::Payloads::Gcp::Runner.run( 63 | command.to_s, 64 | general_params, 65 | specific_params 66 | ) 67 | manifest.add_payload result[:id], result 68 | end 69 | end -------------------------------------------------------------------------------- /examples/gcp/specific_params.rb: -------------------------------------------------------------------------------- 1 | require 'active_model' 2 | class Gcp::SpecificParams 3 | include ActiveModel::Validations 4 | 5 | attr_accessor :gcp_bucket_name 6 | 7 | validates :gcp_bucket_name, presence: true 8 | 9 | def initialize( 10 | gcp_bucket_name:) 11 | 12 | @gcp_bucket_name = gcp_bucket_name 13 | end 14 | end -------------------------------------------------------------------------------- /examples/gcp/state.rb: -------------------------------------------------------------------------------- 1 | class Gcp::State 2 | attr_accessor :results, 3 | :manifest, 4 | :specific_params 5 | 6 | def initialize 7 | @results = {} 8 | @specific_params = nil 9 | end 10 | 11 | # klass: The class that has the validator 12 | # function_name: The function name we will dynamically call 13 | # input_params: pass these values in from the state file as parameters 14 | # output_params: set these values from the returned payload into the statefile 15 | # override_params: pass the param and value but not from the state file 16 | def process( 17 | klass:, 18 | function_name:, 19 | input_params: [], 20 | output_params: [], 21 | override_params: {}, 22 | rule_name: nil 23 | ) 24 | arguments = { 25 | manifest: self.manifest, 26 | specific_params: specific_params 27 | } 28 | 29 | input_params.each do |param| 30 | unless send(param) 31 | puts "failed to run #{klass}.#{function_name} because #{param} was false" 32 | end 33 | arguments[param] = send(param) 34 | end 35 | 36 | override_params.each{|k,v| arguments[k] = v} 37 | 38 | raise "#{klass}: expected '#{function_name}' function to exist" unless klass.respond_to?(function_name.to_sym) 39 | 40 | data = klass.send(function_name, **arguments) 41 | output_params.each do |param| 42 | puts "assigning output param: #{param}: #{data[param]}" 43 | send("#{param}=", data[param]) 44 | end 45 | rule_name ||= function_name 46 | 47 | raise "#{rule_name}: no data returned" if data.nil? 48 | raise "#{rule_name}: no data with result returned" unless data.key?(:result) 49 | @results[rule_name] = data 50 | end 51 | 52 | end -------------------------------------------------------------------------------- /examples/gcp/validations/example.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :example do 2 | spec :should_have_bucket do |t| 3 | bucket_name = t.specific_params.gcp_bucket_name 4 | bucket = assert_load("gcloud-storage-buckets-list").find("name",bucket_name).returns(:all) 5 | 6 | assert_not_nil(bucket) 7 | set_pass_message "Found Google Cloud Storage Bucket named #{bucket_name}" 8 | set_fail_message "Failed to find Google Cloud Storage Bucket named #{bucket_name}" 9 | end 10 | 11 | spec :should_have_bucket_object do |t| 12 | bucket_name = t.specific_params.gcp_bucket_name 13 | object_name = "ships.csv" 14 | 15 | object = assert_load("gcloud-storage-objects-describe__#{bucket_name}").returns(:all) 16 | 17 | assert_eq(object,'content_type','text/csv') 18 | set_pass_message "Found Google Cloud Storage Bucket Object named #{object_name}" 19 | set_fail_message "Failed to find Google Cloud Storage Bucket Object named #{object_name}" 20 | end 21 | end -------------------------------------------------------------------------------- /examples/gcp/validator.rb: -------------------------------------------------------------------------------- 1 | class Gcp::Validator 2 | def self.run( 3 | validations_path:, 4 | general_params:, 5 | specific_params:, 6 | dynamic_params:, 7 | load_order: [] 8 | ) 9 | unless general_params.valid? 10 | puts general_params.errors.full_messages 11 | raise "failed to pass general params validation" 12 | end 13 | 14 | unless specific_params.valid? 15 | puts specific_params.errors.full_messages 16 | raise "failed to specific params validation" 17 | end 18 | 19 | state = Gcp::State.new 20 | 21 | manifest = Cpbvt::Manifest.new( 22 | user_uuid: general_params.user_uuid, 23 | run_uuid: general_params.run_uuid, 24 | output_path: general_params.output_path, 25 | project_scope: general_params.project_scope, 26 | payloads_bucket: general_params.payloads_bucket 27 | ) 28 | manifest.load_from_file! 29 | manifest.pull! 30 | 31 | Cpbvt::Tester::Runner.run!( 32 | validations_path: validations_path, 33 | load_order: load_order, 34 | manifest: manifest, 35 | general_params: general_params, 36 | specific_params: specific_params, 37 | dynamic_params: dynamic_params 38 | ) 39 | end # def self.run 40 | end -------------------------------------------------------------------------------- /examples/output/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExamProCo/cloud-project-bootcamp-validation-tool/060e21774b48293a91ff655a8798c5d0bcb037d6/examples/output/.keep -------------------------------------------------------------------------------- /examples/specific_params: -------------------------------------------------------------------------------- 1 | 2 | From: /workspace/cloud-project-bootcamp-validation-tool/examples/gcp/puller.rb:24 Gcp::Puller.run: 3 | 4 | 4: def self.run(general_params:,specific_params:) 5 | 5: unless general_params.valid? 6 | 6: puts general_params.errors.full_messages 7 | 7: raise "failed to pass general params validation" 8 | 8: end 9 | 9: 10 | 10: unless specific_params.valid? 11 | 11: puts specific_params.errors.full_messages 12 | 12: raise "failed to specific params validation" 13 | 13: end 14 | 14: 15 | 15: manifest = Cpbvt::Manifest.new( 16 | 16: user_uuid: general_params.user_uuid, 17 | 17: run_uuid: general_params.run_uuid, 18 | 18: project_scope: general_params.project_scope, 19 | 19: output_path: general_params.output_path, 20 | 20: payloads_bucket: general_params.payloads_bucket 21 | 21: ) 22 | 22: 23 | 23: creds = Cpbvt::Payloads::Gcp::Command.login general_params.gcp_key_file 24 | => 24: binding.pry 25 | 25: 26 | 26: Async do |task| 27 | 27: self.pull_async task, :gcloud_storage_ls, manifest, general_params, {bucket_name: specific_params.gcp_bucket_name } 28 | 28: end 29 | 29: end 30 | 31 | -------------------------------------------------------------------------------- /examples/tf_beginner_aws_2023/validations/s3.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :s3 do 2 | spec :should_have_bucket_matching_name do |t| 3 | name = t.specific_params.s3_bucket_name 4 | assert_load('s3api-list-buckets','Buckets').find('Name',name) 5 | set_pass_message "Found a bucket matching name: #{name}" 6 | set_fail_message "Failed to find a bucket with matching name: #{name}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /examples/tf_beginner_aws_2023/validations/week1.rb: -------------------------------------------------------------------------------- 1 | Cpbvt::Tester::Runner.describe :week1 do 2 | spec :should_have_bucket_matching_name do |t| 3 | name = t.specific_params.s3_bucket_name 4 | assert_load('s3api-list-buckets','Buckets').find('Name',name) 5 | set_pass_message "Found a bucket matching name: #{name}" 6 | set_fail_message "Failed to find a bucket with matching name: #{name}" 7 | end 8 | 9 | spec :should_have_bucket_static_website_hosting_index do |t| 10 | name = t.specific_params.s3_bucket_name 11 | bucket = assert_load('s3api-list-buckets','Buckets').find('Name',name).returns(:all) 12 | 13 | assert_not_nil(bucket) 14 | 15 | hosting = assert_load("s3api-get-bucket-website__#{name}").returns(:all) 16 | 17 | assert_json(hosting,'IndexDocument','Suffix').expects_eq('index.html') 18 | 19 | set_pass_message "Found s3 static website hosting serviing index.html for #{name}" 20 | set_fail_message "Failed to find s3 static website hosting serviing index.html for #{name}" 21 | end 22 | 23 | spec :should_have_bucket_static_website_hosting_error do |t| 24 | name = t.specific_params.s3_bucket_name 25 | bucket = assert_load('s3api-list-buckets','Buckets').find('Name',name).returns(:all) 26 | 27 | assert_not_nil(bucket) 28 | 29 | hosting = assert_load("s3api-get-bucket-website__#{name}").returns(:all) 30 | 31 | assert_json(hosting,'ErrorDocument','Key').expects_eq('error.html') 32 | 33 | set_pass_message "Found s3 static website hosting serviing index.html for #{name}" 34 | set_fail_message "Failed to find s3 static website hosting serviing index.html for #{name}" 35 | end 36 | 37 | spec :should_have_a_cloudfront_distrubition_to_static_website do |t| 38 | name = t.specific_params.s3_bucket_name 39 | aws_region = t.general_params.region 40 | 41 | items = assert_load('cloudfront-list-distributions','DistributionList').returns('Items') 42 | 43 | distribution = 44 | assert_find(items) do |assert,distribution| 45 | aliases = distribution['Aliases'] 46 | assert.expects_eq(aliases,'Quantity',0) 47 | 48 | origins = distribution['Origins'] 49 | assert.expects_eq(origins,'Quantity',1) 50 | 51 | origin = origins['Items'].first 52 | assert.expects_eq(origin,'Id','MyS3Origin') 53 | end.returns(:all) 54 | 55 | assert_not_nil(distribution) 56 | 57 | assert_json(distribution,'Status').expects_eq('Deployed') 58 | 59 | item = assert_json(distribution,'Origins','Items').returns(:first) 60 | assert_json(item,'DomainName').expects_eq("#{name}.s3.#{aws_region}.amazonaws.com") 61 | 62 | origin_access_control_id = assert_json(item,'OriginAccessControlId').returns(:all) 63 | dist_id = assert_json(distribution,'Id').returns(:all) 64 | dist_domain_name = assert_json(distribution,'DomainName').returns(:all) 65 | 66 | set_state_value :static_website_distribution_id, dist_id 67 | set_state_value :static_website_distribution_domain_name, dist_domain_name 68 | set_state_value :origin_access_control_id, origin_access_control_id 69 | 70 | set_pass_message "Found static website CloudFront distrubution with origin to S3 static website bucket for: #{name}.s3.#{aws_region}.amazonaws.com" 71 | set_fail_message "Failed to find static website CloudFront with origin to S3 static website bucket for: #{name}.s3.#{aws_region}.amazonaws.com" 72 | end 73 | 74 | spec :should_have_ran_invalidation_on_distrubition do |t| 75 | dist_id = t.dynamic_params.static_website_distribution_id 76 | items = assert_load("cloudfront-list-invalidations__#{dist_id}","InvalidationList").returns('Items') 77 | 78 | found = 79 | assert_find(items) do |assert, item| 80 | assert.expects_eq(item,'Status','Completed') 81 | end.returns(:all) 82 | 83 | assert_not_nil found 84 | 85 | set_pass_message "Found static website CloudFront distrubution to have ran invalidations" 86 | set_fail_message "Failed to find static website CloudFront distrubution to have ran invalidations" 87 | end 88 | 89 | spec :should_have_access_origin_policy do |t| 90 | name = t.specific_params.s3_bucket_name 91 | control_id = t.dynamic_params.origin_access_control_id 92 | control_list = assert_load('cloudfront-list-origin-access-controls','OriginAccessControlList').returns(:all) 93 | 94 | assert_json(control_list,'Quantity').expects_gt(0) 95 | 96 | control_item = 97 | assert_find(control_list['Items']) do |assert, item| 98 | assert.expects_end_with(name) 99 | end.returns(:all) 100 | 101 | assert_json(control_item,'SigningProtocol').expects_eq('sigv4') 102 | assert_json(control_item,'SigningBehavior').expects_eq('always') 103 | assert_json(control_item,'OriginAccessControlOriginType').expects_eq('s3') 104 | end 105 | 106 | spec :check_index_file_content_type do |t| 107 | object = assert_load("s3api-get-head-object__index.html").returns(:all) 108 | 109 | assert_json(object,'ContentType').expects_eq('text/html') 110 | assert_json(object,'ContentLength').expects_gt(0) 111 | end 112 | 113 | #should be configured for website 114 | 115 | # cloudfront origin access control policy 116 | 117 | # cloudfront distrubution 118 | 119 | # invalidate cache 120 | end 121 | -------------------------------------------------------------------------------- /lib/cpbvt.rb: -------------------------------------------------------------------------------- 1 | require_relative 'cpbvt/module_defs' 2 | require_relative 'cpbvt/version' 3 | require_relative 'cpbvt/uploader' 4 | require_relative 'cpbvt/downloader' 5 | require_relative 'cpbvt/manifest' 6 | 7 | # --- require cpbvt/payloads/azure/commands/* 8 | aws_commands_path = File.join(File.dirname(__FILE__),'cpbvt','payloads','azure','commands','*.rb') 9 | Dir.glob(aws_commands_path,&method(:require)) 10 | # --- 11 | require_relative 'cpbvt/payloads/azure/general_params' 12 | require_relative 'cpbvt/payloads/azure/runner' 13 | require_relative 'cpbvt/payloads/azure/command' 14 | 15 | # --- require cpbvt/payloads/gcp/commands/* 16 | aws_commands_path = File.join(File.dirname(__FILE__),'cpbvt','payloads','gcp','commands','*.rb') 17 | Dir.glob(aws_commands_path,&method(:require)) 18 | # --- 19 | require_relative 'cpbvt/payloads/gcp/general_params' 20 | require_relative 'cpbvt/payloads/gcp/runner' 21 | require_relative 'cpbvt/payloads/gcp/command' 22 | 23 | # --- require cpbvt/payloads/aws/extractors/* 24 | aws_commands_path = File.join(File.dirname(__FILE__),'cpbvt','payloads','aws','extractors','*.rb') 25 | Dir.glob(aws_commands_path,&method(:require)) 26 | # --- 27 | require_relative 'cpbvt/payloads/aws/general_params' 28 | require_relative 'cpbvt/payloads/aws/extractor' 29 | # --- require cpbvt/payloads/aws/commands/* 30 | aws_commands_path = File.join(File.dirname(__FILE__),'cpbvt','payloads','aws','commands','*.rb') 31 | Dir.glob(aws_commands_path,&method(:require)) 32 | # --- 33 | require_relative 'cpbvt/payloads/aws/runner' 34 | require_relative 'cpbvt/payloads/aws/command' 35 | # --- require cpbvt/payloads/aws/policies/* 36 | aws_commands_path = File.join(File.dirname(__FILE__),'cpbvt','payloads','aws','policies','*.rb') 37 | Dir.glob(aws_commands_path,&method(:require)) 38 | # --- 39 | require_relative 'cpbvt/payloads/aws/policy' 40 | require_relative 'cpbvt/payloads/azure/policy' 41 | 42 | require_relative 'cpbvt/tester/report' 43 | require_relative 'cpbvt/tester/runner' 44 | require_relative 'cpbvt/tester/describe' 45 | require_relative 'cpbvt/tester/spec' 46 | require_relative 'cpbvt/tester/assert_load' 47 | require_relative 'cpbvt/tester/assert_json' 48 | require_relative 'cpbvt/tester/assert_cfn_resource' 49 | require_relative 'cpbvt/tester/assert_find' 50 | require_relative 'cpbvt/tester/assert_select' 51 | -------------------------------------------------------------------------------- /lib/cpbvt/downloader.rb: -------------------------------------------------------------------------------- 1 | require 'aws-sdk-s3' 2 | 3 | class Cpbvt::Downloader 4 | def self.run(file_path:, 5 | object_key:, 6 | aws_region:, 7 | aws_access_key_id:, 8 | aws_secret_access_key:, 9 | payloads_bucket:) 10 | #Aws.config.update({ 11 | # credentials: Aws::Credentials.new( 12 | # aws_access_key_id, 13 | # aws_secret_access_key 14 | # ) 15 | #}) 16 | ## Create an S3 client 17 | #s3_client = Aws::S3::Client.new 18 | 19 | #File.open(file_path, 'wb') do |file| 20 | # s3.get_object({ bucket: payloads_bucket, key: object_key }, target: file) 21 | #end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/cpbvt/manifest.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'json' 3 | require 'zip' 4 | require 'pry' 5 | 6 | class Cpbvt::Manifest 7 | attr_accessor :payloads, # store all the payloads data structures 8 | :user_uuid, 9 | :run_uuid, 10 | :output_path, 11 | :project_scope, 12 | :starts_at, 13 | :ends_at 14 | 15 | def initialize(user_uuid:, run_uuid:, output_path:, project_scope:, payloads_bucket:) 16 | @starts_at = Time.now.to_f 17 | @user_uuid = user_uuid 18 | @run_uuid = run_uuid 19 | @project_scope = project_scope 20 | @output_path = output_path 21 | @payloads = {} 22 | end # init 23 | 24 | def archive! 25 | path = self.output_path 26 | zipfile_name = self.archive_path 27 | if Dir[File.join(path,'*')].any? 28 | if File.exist?(zipfile_name) 29 | File.delete(zipfile_name) 30 | end 31 | Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| 32 | files = Dir.glob(File.join(path,'**','*')) 33 | basepath = File.join(@output_path, @project_scope, "user-#{@user_uuid}","/").to_s 34 | files.each do |file| 35 | zip_file_path = file.sub(basepath,"") 36 | puts zip_file_path 37 | zipfile.add(zip_file_path, file) 38 | end 39 | end 40 | end 41 | end # archive! 42 | 43 | # Pull S3 if the local files don't exist 44 | def pull! 45 | # if the manifest doesn't exist lets assume nothing is here. 46 | path = self.output_file 47 | unless File.exist?(path) 48 | end 49 | end 50 | 51 | # Load a manifest file 52 | def load_from_file! 53 | path = self.output_file 54 | if File.exist?(path) 55 | json = File.read(path) 56 | data = JSON.parse(json) 57 | @user_uuid = data['metadata']['user_uuid'] 58 | @run_uuid = data['metadata']['run_uuid'] 59 | @project_scope = data['metadata']['project_scope'] 60 | @payloads = {} 61 | data['payloads'].each do |key,payload| 62 | @payloads[key] = { 63 | object_key: payload['object_key'], 64 | output_file: payload['output_file'], 65 | command: payload['command'] 66 | } 67 | end 68 | else 69 | raise "Manifest.load_from_file! Couldn't find Manifest file at: #{path}" 70 | end 71 | end 72 | 73 | def output_file 74 | File.join( 75 | @output_path, 76 | @project_scope, 77 | "user-#{@user_uuid}", 78 | "run-#{@run_uuid}", 79 | "manifest.json" 80 | ) 81 | end 82 | 83 | def output_path 84 | File.join( 85 | @output_path, 86 | @project_scope, 87 | "user-#{@user_uuid}", 88 | "run-#{@run_uuid}" 89 | ) 90 | end 91 | 92 | def archive_path 93 | File.join( 94 | @output_path, 95 | @project_scope, 96 | "user-#{@user_uuid}", 97 | "run-#{@run_uuid}.zip" 98 | ) 99 | end 100 | 101 | def archive_object_key 102 | File.join( 103 | @project_scope, 104 | "user-#{@user_uuid}", 105 | "run-#{@run_uuid}.zip" 106 | ) 107 | end 108 | 109 | def object_key 110 | File.join( 111 | @project_scope, 112 | "user-#{@user_uuid}", 113 | "run-#{@run_uuid}", 114 | "manifest.json" 115 | ) 116 | end 117 | 118 | def add_payload key, data 119 | key = _format_key(key) 120 | @payloads[key] = data 121 | end 122 | 123 | def has_payload? key 124 | key = _format_key(key) 125 | @payloads.has_key? key 126 | end 127 | 128 | def get_output key 129 | key = _format_key(key) 130 | return false unless @payloads.key?(key) 131 | output_file = @payloads[key][:output_file] 132 | json_data = File.read(output_file) 133 | hash = 134 | if json_data == '' 135 | {} 136 | else 137 | JSON.parse(json_data) 138 | end 139 | return hash 140 | end 141 | 142 | # similar to get_output but has error handling 143 | def get_output! key 144 | key = _format_key(key) 145 | unless @payloads.key?(key) 146 | raise StandardError.new "#{key} not found in manifest" 147 | return 148 | end 149 | output_file = @payloads[key][:output_file] 150 | json_data = File.read(output_file) 151 | hash = 152 | if json_data == '' 153 | {} 154 | else 155 | JSON.parse(json_data) 156 | end 157 | return hash 158 | end 159 | 160 | def _format_key key 161 | # if the key is symbol conver it to a string 162 | # if the key using hypens covert it to lowercase 163 | key.to_s.gsub(/-/,'_') 164 | end 165 | 166 | def payload_keys 167 | @payloads.keys 168 | end 169 | 170 | # write content to a file 171 | def write_file! 172 | @ends_at = Time.now.to_f 173 | File.open(self.output_file, 'w') do |f| 174 | f.write(JSON.pretty_generate(self.contents)) 175 | end 176 | end 177 | 178 | def contents 179 | { 180 | metadata: { 181 | user_uuid: @user_uuid, 182 | run_uuid: @run_uuid, 183 | project_scope: @project_scope, 184 | }, 185 | benchmark: { 186 | starts_at: @starts_at, 187 | ends_at: @ends_at, 188 | duration_in_ms: ((@ends_at - @starts_at)*1000).to_i 189 | }, 190 | payloads: @payloads 191 | } 192 | end 193 | 194 | # returns results of each payload 195 | # this is so we can find in summary 196 | # which succeeded or which ones failed 197 | def results 198 | @payloads.map do |k,v| 199 | {id: v[:id], error: v[:error] } 200 | end 201 | end 202 | end # class 203 | -------------------------------------------------------------------------------- /lib/cpbvt/module_defs.rb: -------------------------------------------------------------------------------- 1 | # Define all our modules so we don't have nest modules 2 | # names in our files 3 | module Cpbvt 4 | module Validations; end 5 | module Tester; end 6 | module Payloads 7 | module Gcp 8 | module Commands; end 9 | module Extractors; end 10 | end 11 | module Azure 12 | module Commands; end 13 | module Extractors; end 14 | end 15 | module Aws 16 | module Commands; end 17 | module Extractors; end 18 | module Policies; end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/command.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | class Cpbvt::Payloads::Aws::Command 4 | include Cpbvt::Payloads::Aws::Commands::Acm 5 | include Cpbvt::Payloads::Aws::Commands::Apigatewayv2 6 | include Cpbvt::Payloads::Aws::Commands::Cloudformation 7 | include Cpbvt::Payloads::Aws::Commands::Cloudfront 8 | include Cpbvt::Payloads::Aws::Commands::Codebuild 9 | include Cpbvt::Payloads::Aws::Commands::Codepipeline 10 | include Cpbvt::Payloads::Aws::Commands::CognitoIdp 11 | include Cpbvt::Payloads::Aws::Commands::Dynamodb 12 | include Cpbvt::Payloads::Aws::Commands::Dynamodbstreams 13 | include Cpbvt::Payloads::Aws::Commands::Ec2 14 | include Cpbvt::Payloads::Aws::Commands::Ecr 15 | include Cpbvt::Payloads::Aws::Commands::Ecs 16 | include Cpbvt::Payloads::Aws::Commands::Elbv2 17 | include Cpbvt::Payloads::Aws::Commands::Lambda 18 | include Cpbvt::Payloads::Aws::Commands::Rds 19 | include Cpbvt::Payloads::Aws::Commands::Route53 20 | include Cpbvt::Payloads::Aws::Commands::S3api 21 | include Cpbvt::Payloads::Aws::Commands::Servicediscovery 22 | 23 | def self.session_token target_aws_account_id, external_id 24 | # the aws credentials are different for the server/validators 25 | command = <<~COMMAND 26 | AWS_ACCESS_KEY_ID=#{ENV['VALIDATOR_AWS_ACCESS_KEY_ID']} AWS_SECRET_ACCESS_KEY=#{ENV['VALIDATOR_AWS_SECRET_ACCESS_KEY']} \ 27 | aws sts assume-role \ 28 | --role-arn "arn:aws:iam::#{target_aws_account_id}:role/Validator-#{external_id}" \ 29 | --role-session-name "crossAccountAccess" \ 30 | --external-id #{external_id} 31 | COMMAND 32 | puts "[Executing] #{command}" 33 | 34 | begin 35 | stdout_str, exit_code = Open3.capture2(command)#, :stdin_data=>post_content) 36 | payload = JSON.parse(stdout_str) 37 | result = payload['Credentials'] 38 | rescue => e 39 | puts "[ERROR] #{e.message}" 40 | result = e.message 41 | end 42 | return result 43 | end 44 | end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/acm.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Acm 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/acm/describe-certificate.html 7 | def acm_describe_certificate(certificate_arn:) 8 | <<~COMMAND 9 | aws acm describe-certificate \ 10 | --certificate-arn #{certificate_arn} 11 | COMMAND 12 | end 13 | 14 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/acm/list-certificates.html 15 | def acm_list_certificates 16 | <<~COMMAND 17 | aws acm list-certificates 18 | COMMAND 19 | end 20 | # ------ 21 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/apigatewayv2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Apigatewayv2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | 5 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigatewayv2/get-routes.html 6 | def apigatewayv2_get_routes(api_id:) 7 | <<~COMMAND 8 | aws apigatewayv2 get-routes \ 9 | --api-id #{api_id} 10 | COMMAND 11 | end 12 | 13 | # https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/get-authorizers.html 14 | def apigatewayv2_get_authorizers(api_id:) 15 | <<~COMMAND 16 | aws apigatewayv2 get-authorizers \ 17 | --api-id #{api_id} 18 | COMMAND 19 | end 20 | 21 | # https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/get-integrations.html 22 | def apigatewayv2_get_integrations(api_id:) 23 | <<~COMMAND 24 | aws apigatewayv2 get-integrations \ 25 | --api-id #{api_id} 26 | COMMAND 27 | end 28 | 29 | # https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/get-apis.html 30 | def apigatewayv2_get_apis 31 | <<~COMMAND 32 | aws apigatewayv2 get-apis 33 | COMMAND 34 | end 35 | 36 | # ------ 37 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/cloudformation.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Cloudformation 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudformation/list-stacks.html 7 | # If we don't specify a stack status filter, the output includes all stacks. 8 | # We really only want to see currently deployed stacks. 9 | def cloudformation_list_stacks 10 | <<~COMMAND 11 | aws cloudformation list-stacks \ 12 | --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE 13 | COMMAND 14 | end 15 | 16 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudformation/list-stack-resources.html 17 | def cloudformation_list_stack_resources(stack_name:) 18 | <<~COMMAND 19 | aws cloudformation list-stack-resources \ 20 | --stack-name #{stack_name} 21 | COMMAND 22 | end 23 | 24 | # ------ 25 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/cloudfront.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Cloudfront 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/list-distributions.html 7 | def cloudfront_list_distributions 8 | <<~COMMAND 9 | aws cloudfront list-distributions 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/get-distribution.html 14 | def cloudfront_get_distribution(distribution_id:) 15 | <<~COMMAND 16 | aws cloudfront get-distribution \ 17 | --id #{distribution_id} 18 | COMMAND 19 | end 20 | 21 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/list-invalidations.html 22 | def cloudfront_list_invalidations(distribution_id:) 23 | <<~COMMAND 24 | aws cloudfront list-invalidations \ 25 | --distribution-id #{distribution_id} 26 | COMMAND 27 | end 28 | 29 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/list-cloud-front-origin-access-identities.html 30 | # This returns blank and I don't know why.... 31 | def cloudfront_list_cloud_front_origin_access_identities 32 | <<~COMMAND 33 | aws cloudfront list-cloud-front-origin-access-identities 34 | COMMAND 35 | end 36 | 37 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/get-cloud-front-origin-access-identity.html 38 | # Can't do this one if the above one doesn't work 39 | def cloudfront_get_cloud_front_origin_access_identity(identity_id:) 40 | <<~COMMAND 41 | aws cloudfront get-cloud-front-origin-access-identity \ 42 | --id #{identity_id} 43 | COMMAND 44 | end 45 | 46 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/get-cloud-front-origin-access-identity-config.html 47 | def cloudfront_get_cloud_front_origin_access_identity_config(identity_id:) 48 | <<~COMMAND 49 | aws cloudfront get-cloud-front-origin-access-identity-config \ 50 | --id #{identity_id} 51 | COMMAND 52 | end 53 | 54 | # https://docs.aws.amazon.com/cli/latest/reference/cloudfront/list-origin-access-controls.html 55 | def cloudfront_list_origin_access_controls 56 | <<~COMMAND 57 | aws cloudfront list-origin-access-controls 58 | COMMAND 59 | end 60 | 61 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/get-origin-access-control.html 62 | def cloudfront_get_origin_access_control(control_id:) 63 | <<~COMMAND 64 | aws cloudfront get-origin-access-control \ 65 | --id #{control_id} 66 | COMMAND 67 | end 68 | 69 | # ------ 70 | end; end 71 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/codebuild.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Codebuild 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/codebuild/list-projects.html 7 | def codebuild_list_projects 8 | <<~COMMAND 9 | aws codebuild list-projects 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/codebuild/list-builds.html 14 | def codebuild_list_builds 15 | <<~COMMAND 16 | aws codebuild list-builds 17 | COMMAND 18 | end 19 | 20 | def codebuild_batch_get_projects(project_name:) 21 | <<~COMMAND 22 | aws codebuild batch-get-projects \ 23 | --names "#{project_name}" 24 | COMMAND 25 | end 26 | 27 | # ------ 28 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/codepipeline.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Codepipeline 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/codepipeline/list-pipelines.html 7 | def codepipeline_list_pipelines 8 | <<~COMMAND 9 | aws codepipeline list-pipelines 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/codepipeline/get-pipeline.html 14 | def codepipeline_get_pipeline(pipeline_name:) 15 | <<~COMMAND 16 | aws codepipeline get-pipeline \ 17 | --name #{pipeline_name} 18 | COMMAND 19 | end 20 | 21 | # ------ 22 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/cognito_idp.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::CognitoIdp 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/describe-user-pool.html 7 | def cognito_idp_describe_user_pool(user_pool_id:) 8 | <<~COMMAND 9 | aws cognito-idp describe-user-pool \ 10 | --user-pool-id #{user_pool_id} 11 | COMMAND 12 | end 13 | 14 | # we manually set max results to 10, we shouldn't really see that many 15 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/list-user-pools.html 16 | def cognito_idp_list_user_pools 17 | <<~COMMAND 18 | aws cognito-idp list-user-pools \ 19 | --max-results 10 20 | COMMAND 21 | end 22 | 23 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/list-user-pool-clients.html 24 | def cognito_idp_list_user_pool_clients(user_pool_id:) 25 | <<~COMMAND 26 | aws cognito-idp list-user-pool-clients \ 27 | --user-pool-id #{user_pool_id} 28 | COMMAND 29 | end 30 | 31 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/list-users.html 32 | def cognito_idp_list_users(user_pool_id:) 33 | <<~COMMAND 34 | aws cognito-idp list-users \ 35 | --user-pool-id #{user_pool_id} 36 | COMMAND 37 | end 38 | 39 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/describe-user-pool-client.html 40 | def cognito_idp_describe_user_pool_client(user_pool_id:, client_id:) 41 | <<~COMMAND 42 | aws cognito-idp describe-user-pool-client \ 43 | --user-pool-id #{user_pool_id} \ 44 | --client-id #{client_id} 45 | COMMAND 46 | end 47 | 48 | # ------ 49 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/dynamodb.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Dynamodb 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/dynamodb/list-tables.html 7 | def dynamodb_list_tables 8 | <<~COMMAND 9 | aws dynamodb list-tables 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/dynamodb/describe-table.html 14 | def dynamodb_describe_table(table_name:) 15 | <<~COMMAND 16 | aws dynamodb \ 17 | describe-table \ 18 | --table-name #{table_name} 19 | COMMAND 20 | end 21 | 22 | # ------ 23 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/dynamodbstreams.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Dynamodbstreams 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/dynamodbstreams/list-streams.html 7 | def dynamodbstreams_list_streams 8 | <<~COMMAND 9 | aws dynamodbstreams list-streams 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/dynamodbstreams/describe-stream.html 14 | def dynamodbstreams_describe_stream(stream_arn:) 15 | <<~COMMAND 16 | aws dynamodbstreams describe-stream \ 17 | --stream-arn #{stream_arn} 18 | COMMAND 19 | end 20 | 21 | # ------ 22 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/ec2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Ec2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-vpcs.html 7 | def ec2_describe_vpcs 8 | <<~COMMAND 9 | aws ec2 describe-vpcs 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-subnets.html 14 | def ec2_describe_subnets 15 | <<~COMMAND 16 | aws ec2 describe-subnets 17 | COMMAND 18 | end 19 | 20 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-route-tables.html 21 | def ec2_describe_route_tables 22 | <<~COMMAND 23 | aws ec2 describe-route-tables 24 | COMMAND 25 | end 26 | 27 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-internet-gateways.html 28 | def ec2_describe_internet_gateways 29 | <<~COMMAND 30 | aws ec2 describe-internet-gateways 31 | COMMAND 32 | end 33 | 34 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-security-groups.html 35 | def ec2_describe_security_groups 36 | <<~COMMAND 37 | aws ec2 describe-security-groups 38 | COMMAND 39 | end 40 | 41 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-route-tables.html 42 | def ec2_describe_route_tables 43 | <<~COMMAND 44 | aws ec2 describe-route-tables 45 | COMMAND 46 | end 47 | 48 | # ------ 49 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/ecr.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Ecr 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/describe-repositories.html 7 | def ecr_describe_repositories 8 | <<~COMMAND 9 | aws ecr describe-repositories 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/describe-images.html 14 | def ecr_describe_images(repository_name:) 15 | <<~COMMAND 16 | aws ecr describe-images \ 17 | --repository-name #{repository_name} 18 | COMMAND 19 | end 20 | 21 | # ------ 22 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/ecs.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Ecs 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/describe-clusters.html 7 | def ecs_describe_clusters(cluster_name:) 8 | <<~COMMAND 9 | aws ecs describe-clusters \ 10 | --clusters #{cluster_name} 11 | COMMAND 12 | end 13 | 14 | # https://docs.aws.amazon.com/cli/latest/reference/ecs/list-services.html 15 | def ecs_list_services(cluster_name:) 16 | <<~COMMAND 17 | aws ecs list-services \ 18 | --cluster #{cluster_name} 19 | COMMAND 20 | end 21 | 22 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/describe-services.html 23 | # services is required - a list of services to describe 24 | # can only return 10 service names 25 | def ecs_describe_services(cluster_name:,services:) 26 | <<~COMMAND 27 | aws ecs describe-services \ 28 | --cluster #{cluster_name} 29 | --services #{services.join(' ')} 30 | COMMAND 31 | end 32 | 33 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/describe-tasks.html 34 | def ecs_describe_tasks(cluster_name:, task_ids:) 35 | <<~COMMAND 36 | aws ecs describe-tasks \ 37 | --tasks #{task_ids.join(' ')} \ 38 | --cluster #{cluster_name} 39 | COMMAND 40 | end 41 | 42 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/list-tasks.html 43 | def ecs_list_tasks(cluster_name:, family:) 44 | <<~COMMAND 45 | aws ecs list-tasks \ 46 | --cluster #{cluster_name} \ 47 | --family #{family} 48 | COMMAND 49 | end 50 | 51 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/list-task-definitions.html 52 | def ecs_list_task_definitions 53 | <<~COMMAND 54 | aws ecs list-task-definitions 55 | COMMAND 56 | end 57 | 58 | # ------ 59 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/elbv2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Elbv2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elbv2/describe-load-balancers.html 7 | def elbv2_describe_load_balancers 8 | <<~COMMAND 9 | aws elbv2 describe-load-balancers 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elbv2/describe-listeners.html 14 | def elbv2_describe_listeners(load_balancer_arn:) 15 | <<~COMMAND 16 | aws elbv2 describe-listeners \ 17 | --load-balancer-arn #{load_balancer_arn} 18 | COMMAND 19 | end 20 | 21 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elbv2/describe-load-balancer-attributes.html 22 | def elbv2_describe_load_balancer_attributes(load_balancer_arn:) 23 | <<~COMMAND 24 | aws elbv2 describe-load-balancer-attributes \ 25 | --load-balancer-arn #{load_balancer_arn} 26 | COMMAND 27 | end 28 | 29 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elbv2/describe-rules.html 30 | def elbv2_describe_rules(listern_arn:) 31 | <<~COMMAND 32 | aws elbv2 describe-rules \ 33 | --listener-arn #{listen_arn} 34 | COMMAND 35 | end 36 | 37 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elbv2/describe-target-groups.html 38 | def elbv2_describe_target_groups 39 | <<~COMMAND 40 | aws elbv2 describe-target-groups 41 | COMMAND 42 | end 43 | 44 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elbv2/describe-target-group-attributes.html 45 | def elbv2_describe_target_group_attributes(target_group_arn:) 46 | <<~COMMAND 47 | aws elbv2 describe-target-group-attributes \ 48 | --target-group-arn #{target_group_arn} 49 | COMMAND 50 | end 51 | 52 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elbv2/describe-target-health.html 53 | def elbv2_describe_target_health(target_group_arn:) 54 | <<~COMMAND 55 | aws elbv2 describe-target-health \ 56 | --target-group-arn #{target_group_arn} 57 | COMMAND 58 | end 59 | 60 | # ------ 61 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/lambda.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Lambda 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | 7 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/get-function.html 8 | def lambda_get_function(function_name:) 9 | <<~COMMAND 10 | aws lambda get-function \ 11 | --function-name #{function_name} 12 | COMMAND 13 | end 14 | 15 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/list-functions.html 16 | def lambda_list_functions 17 | <<~COMMAND 18 | aws lambda list-functions 19 | COMMAND 20 | end 21 | 22 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/list-layers.html 23 | def lambda_list_layers 24 | <<~COMMAND 25 | aws lambda list-layers 26 | COMMAND 27 | end 28 | 29 | # ------ 30 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/rds.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Rds 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/describe-db-instances.html 7 | def rds_describe_db_instances 8 | <<~COMMAND 9 | aws rds describe-db-instances 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/describe-db-subnet-groups.html 14 | def rds_describe_db_subnet_groups 15 | <<~COMMAND 16 | aws rds describe-db-subnet-groups 17 | COMMAND 18 | end 19 | 20 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/describe-db-snapshots.html 21 | def rds_describe_db_snapshots 22 | <<~COMMAND 23 | aws rds describe-db-snapshots 24 | COMMAND 25 | end 26 | # ------ 27 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/route53.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Route53 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/route53/list-hosted-zones.html 7 | def route53_list_hosted_zones 8 | <<~COMMAND 9 | aws route53 list-hosted-zones 10 | COMMAND 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/route53/get-hosted-zone.html 14 | def route53_get_hosted_zone(hosted_zone_id:) 15 | <<~COMMAND 16 | aws route53 get-hosted-zone \ 17 | --id #{hosted_zone_id} 18 | COMMAND 19 | end 20 | 21 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/route53/list-resource-record-sets.html 22 | def route53_list_resource_record_sets(hosted_zone_id:) 23 | <<~COMMAND 24 | aws route53 list-resource-record-sets \ 25 | --hosted-zone-id #{hosted_zone_id} 26 | COMMAND 27 | end 28 | 29 | # ------ 30 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/s3api.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::S3api 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # has no region but we just pass it in anyway to make 7 | # our code my dry elsewhere 8 | # We can't use s2 ls because it won't return json 9 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/list-buckets.html 10 | def s3api_list_buckets 11 | <<~COMMAND 12 | aws s3api list-buckets 13 | COMMAND 14 | end 15 | 16 | #This action is useful to determine if a bucket exists and you have 17 | #permission to access it. The action returns a 200 OK if the bucket 18 | #exists and you have permission to access it. 19 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/head-bucket.html 20 | def s3api_head_bucket(bucket:) 21 | <<~COMMAND 22 | aws s3api \ 23 | head-bucket --bucket #{bucket} 24 | COMMAND 25 | end 26 | 27 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-notification-configuration.html 28 | def s3api_get_bucket_notification_configuration(bucket:) 29 | <<~COMMAND 30 | aws s3api get-bucket-notification-configuration \ 31 | --bucket #{bucket} 32 | COMMAND 33 | end 34 | 35 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-policy.html 36 | def s3api_get_bucket_policy(bucket:) 37 | <<~COMMAND 38 | aws s3api get-bucket-policy \ 39 | --bucket #{bucket} 40 | COMMAND 41 | end 42 | 43 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-cors.html 44 | def s3api_get_bucket_cors(bucket:) 45 | <<~COMMAND 46 | aws s3api get-bucket-cors \ 47 | --bucket #{bucket} 48 | COMMAND 49 | end 50 | 51 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-website.html 52 | def s3api_get_bucket_website(bucket:) 53 | <<~COMMAND 54 | aws s3api get-bucket-website \ 55 | --bucket #{bucket} 56 | COMMAND 57 | end 58 | 59 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/head-object.html 60 | def s3api_get_head_object(bucket:, key:) 61 | <<~COMMAND 62 | aws s3api head-object \ 63 | --bucket #{bucket} \ 64 | --key #{key} 65 | COMMAND 66 | end 67 | 68 | # special since we are downloading a file 69 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-object.html 70 | def s3api_get_object(bucket:, key:) 71 | <<~COMMAND 72 | aws s3api get-object \ 73 | --bucket #{bucket} \ 74 | --key #{key} 75 | COMMAND 76 | end 77 | 78 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-public-access-block.html 79 | def s3api_get_public_access_block(bucket:) 80 | <<~COMMAND 81 | aws s3api get-public-access-block \ 82 | --bucket #{bucket} 83 | COMMAND 84 | end 85 | 86 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/list-objects-v2.html 87 | def s3api_list_objects_v2(bucket:, prefix:) 88 | <<~COMMAND 89 | aws s3api list-objects-v2 \ 90 | --bucket #{bucket} \ 91 | --prefix #{prefix} 92 | COMMAND 93 | end 94 | 95 | # ------ 96 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/commands/servicediscovery.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Commands::Servicediscovery 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | def servicediscovery_list_services 6 | command = <<~COMMAND.strip.gsub("\n", " ") 7 | aws servicediscovery list-services 8 | COMMAND 9 | end 10 | 11 | def servicediscovery_list_namespaces 12 | command = <<~COMMAND.strip.gsub("\n", " ") 13 | aws servicediscovery list-namespaces 14 | COMMAND 15 | end 16 | # ------ 17 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/cross-account-role-template.yaml: -------------------------------------------------------------------------------- 1 | Resources: 2 | CrossAccountRole: 3 | Type: AWS::IAM::Role 4 | Properties: 5 | RoleName: CrossAccountRole 6 | AssumeRolePolicyDocument: 7 | Version: "2012-10-17" 8 | Statement: 9 | - Effect: Allow 10 | Principal: 11 | AWS: 12 | Fn::Sub: "arn:aws:iam::${SourceAccountId}:user/cloud-project-validation-tool" 13 | Action: sts:AssumeRole 14 | Condition: 15 | StringEquals: 16 | "sts:ExternalId": 17 | Ref: ExternalId 18 | Policies: [] -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractor.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Payloads::Aws::Extractor 2 | include Cpbvt::Payloads::Aws::Extractors::Acm 3 | include Cpbvt::Payloads::Aws::Extractors::Apigatewayv2 4 | include Cpbvt::Payloads::Aws::Extractors::Cloudformation 5 | include Cpbvt::Payloads::Aws::Extractors::Cloudfront 6 | include Cpbvt::Payloads::Aws::Extractors::Codebuild 7 | include Cpbvt::Payloads::Aws::Extractors::Codepipeline 8 | include Cpbvt::Payloads::Aws::Extractors::CognitoIdp 9 | include Cpbvt::Payloads::Aws::Extractors::Dynamodb 10 | include Cpbvt::Payloads::Aws::Extractors::Dynamodbstreams 11 | include Cpbvt::Payloads::Aws::Extractors::Ecs 12 | include Cpbvt::Payloads::Aws::Extractors::Ecr 13 | include Cpbvt::Payloads::Aws::Extractors::Elbv2 14 | include Cpbvt::Payloads::Aws::Extractors::Lambda 15 | include Cpbvt::Payloads::Aws::Extractors::Route53 16 | include Cpbvt::Payloads::Aws::Extractors::S3api 17 | end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/acm.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Acm 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # extract the certifcate arn from the AWS CLI command json output for acm_list_certificates 7 | def acm_list_certificates__certificate_arn(data,filters={}) 8 | if data['CertificateSummaryList'] 9 | data['CertificateSummaryList'].map do |x| 10 | arn = x['CertificateArn'] 11 | iter_id = arn.split("/").last 12 | { 13 | iter_id: iter_id, 14 | certificate_arn: arn 15 | } 16 | end 17 | end 18 | end 19 | 20 | # ------ 21 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/apigatewayv2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Apigatewayv2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def apigatewayv2_get_apis__app_id(data,filters={}) 7 | data['Items'].map do |x| 8 | { 9 | iter_id: x['ApiId'], 10 | api_id: x['ApiId'] 11 | } 12 | end 13 | end 14 | 15 | # ------ 16 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/cloudformation.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Cloudformation 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def cloudformation_list_stacks__stack_name(data,filters={}) 7 | data['StackSummaries'].map do |x| 8 | arn = x['StackId'] 9 | iter_id = arn.split("/").last 10 | { 11 | iter_id: iter_id, 12 | stack_name: x['StackName'] 13 | } 14 | end 15 | end 16 | 17 | # We often need to just grab a specific resource from a stack 18 | # based on its resource type. 19 | def cloudformation_list_stacks__by_stack_resource_type(manifest,stack_name,resource_type) 20 | cfn_stacks = manifest.get_output!('cloudformation-list-stacks') 21 | cicd_stack = cfn_stacks['StackSummaries'].find do |stack| 22 | stack['StackName'] == stack_name 23 | end 24 | # extract the stack id 25 | cicd_stack_id = cicd_stack['StackId'].split('/').last 26 | 27 | cicd_stack_resources = manifest.get_output!("cloudformation-list-stack-resources__#{cicd_stack_id}") 28 | stack_resource = cicd_stack_resources['StackResourceSummaries'].find do |resource| 29 | resource["ResourceType"] == resource_type 30 | end 31 | return stack_resource 32 | end 33 | 34 | # ------ 35 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/cloudfront.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Cloudfront 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def cloudfront_list_distributions__distribution_id(data,filters={}) 7 | data['DistributionList']['Items'].filter_map do |d| 8 | result = true 9 | 10 | if filters.key?(:aliases) && filters[:aliases].any? 11 | if d.key?('Aliases') 12 | if d['Aliases'].key?('Items') 13 | found_aliases = d['Aliases']['Items'] 14 | result = found_aliases.any? do |found_alias| 15 | filters[:aliases].include?(found_alias) 16 | end 17 | else 18 | false 19 | end 20 | else 21 | false 22 | end 23 | end 24 | 25 | if result 26 | { 27 | iter_id: d['Id'], 28 | distribution_id: d['Id'] 29 | } 30 | end 31 | end # .filter_map 32 | end 33 | 34 | def cloudfront_list_origin_access_controls__control_id(data,filters={}) 35 | data['OriginAccessControlList']['Items'].map do |x| 36 | id = x['Id'] 37 | iter_id = x['Id'] 38 | { 39 | iter_id: iter_id, 40 | control_id: id 41 | } 42 | end 43 | end 44 | 45 | # ------ 46 | end; end 47 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/codebuild.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Codebuild 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # extract the certifcate arn from the AWS CLI command json output for acm_list_certificates 7 | def codebuild_list_projects__project_name(data,filters={}) 8 | data['projects'].map do |project_name| 9 | { 10 | iter_id: project_name, 11 | project_name: project_name 12 | } 13 | end 14 | end 15 | 16 | # ------ 17 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/codepipeline.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Codepipeline 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def codepipeline_list_pipelines__pipeline_name data, filters={} 7 | data['pipelines'].map do |t| 8 | { 9 | iter_id: t['name'], 10 | pipeline_name: t['name'] 11 | } 12 | end 13 | end 14 | 15 | # ------ 16 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/cognito_idp.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::CognitoIdp 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def cognito_idp_list_user_pools__user_pool_id data, filters={} 7 | data['UserPools'].map do |t| 8 | { 9 | iter_id: t['Id'], 10 | user_pool_id: t['Id'] 11 | } 12 | end 13 | end 14 | 15 | # ------ 16 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/dynamodb.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Dynamodb 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def dynamodb_list_tables__table_name data, filters={} 7 | data['TableNames'].map do |table_name| 8 | { 9 | iter_id: table_name, 10 | table_name: table_name 11 | } 12 | end 13 | end 14 | 15 | # ------ 16 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/dynamodbstreams.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Dynamodbstreams 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def dynamodbstreams_list_streams__stream_arn data, filters={} 7 | data['Streams'].map do |t| 8 | { 9 | iter_id: t['StreamLabel'], 10 | stream_arn: t['StreamArn'] 11 | } 12 | end 13 | end 14 | 15 | # ------ 16 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/ecr.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Ecr 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def ecr_describe_repositories__repository_name data, filters={} 7 | data['repositories'].map do |t| 8 | { 9 | iter_id: t['registryId'], 10 | repository_name: t['repositoryName'] 11 | } 12 | end 13 | end 14 | 15 | # ------ 16 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/ecs.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Ecs 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # a limit of up to 100 tasks 7 | def ecs_list_tasks__task_id data, filters={} 8 | if data['taskArns'] 9 | data['taskArns'].map do |arn| 10 | task_id = arn.split('/').last 11 | { 12 | iter_id: task_id, 13 | task_id: task_id 14 | } 15 | end 16 | end 17 | end 18 | 19 | # ------ 20 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/elbv2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Elbv2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def elbv2_describe_load_balancers__load_balancer_arn data, filters={} 7 | data['LoadBalancers'].map do |t| 8 | arn = t['LoadBalancerArn'] 9 | iter_id = arn.split('/').last 10 | { 11 | iter_id: iter_id, 12 | load_balancer_arn: arn 13 | } 14 | end 15 | end 16 | 17 | def elbv2_describe_target_groups__target_group_arn data, filters={} 18 | data['TargetGroups'].map do |t| 19 | arn = t['TargetGroupArn'] 20 | iter_id = arn.split('/').last 21 | { 22 | iter_id: iter_id, 23 | target_group_arn: arn 24 | } 25 | end 26 | end 27 | # ------ 28 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/lambda.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Lambda 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def lambda_list_functions__function_name data, filters={} 7 | data['Functions'].map do |t| 8 | name = t['FunctionName'] 9 | { 10 | iter_id: name, 11 | function_name: name 12 | } 13 | end 14 | end 15 | 16 | # ------ 17 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/route53.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::Route53 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def route53_list_hosted_zones__hosted_zone_id data, filters={} 7 | data['HostedZones'].map do |t| 8 | id = t['Id'].split("/").last 9 | { 10 | iter_id: id, 11 | hosted_zone_id: id 12 | } 13 | end 14 | end 15 | 16 | # ------ 17 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/extractors/s3api.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Extractors::S3api 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def s3api_list_buckets__bucket data, filters={} 7 | data['Buckets'].filter_map do |t| 8 | result = true 9 | 10 | name = t['Name'] 11 | if filters.key?(:bucket_names) && filters[:bucket_names].any? 12 | result = filters[:bucket_names].include?(name) 13 | end 14 | 15 | if result 16 | { 17 | iter_id: name, 18 | bucket: name 19 | } 20 | end 21 | end # .filter_map 22 | end 23 | 24 | # ------ 25 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/general_params.rb: -------------------------------------------------------------------------------- 1 | require 'active_model' 2 | class Cpbvt::Payloads::Aws::GeneralParams 3 | include ActiveModel::Validations 4 | 5 | # target_aws_account_id = bootcamper's account 6 | # source_aws_account_id = the account checking the bootcamper account 7 | attr_accessor :project_scope, 8 | :user_uuid, 9 | :run_uuid, 10 | :external_id, 11 | :region, 12 | :user_region, 13 | :output_path, 14 | :aws_access_key_id, 15 | :aws_secret_access_key, 16 | :tmp_aws_access_key_id, 17 | :tmp_aws_secret_access_key, 18 | :tmp_aws_session_token, 19 | :payloads_bucket, 20 | :target_aws_account_id, 21 | :source_aws_account_id 22 | 23 | # we aren't validating the session_token since 24 | # will pul it after validation 25 | 26 | validates :project_scope, presence: true 27 | validates :user_uuid , presence: true 28 | validates :external_id, presence: true 29 | validates :run_uuid, presence: true 30 | validates :region, presence: true 31 | validates :user_region, presence: true 32 | validates :output_path, presence: true 33 | validates :aws_access_key_id, presence: true 34 | validates :aws_secret_access_key, presence: true 35 | validates :payloads_bucket, presence: true 36 | validates :target_aws_account_id, presence: true 37 | validates :source_aws_account_id, presence: true 38 | 39 | def initialize( 40 | project_scope:, 41 | user_uuid:, 42 | run_uuid:, 43 | external_id:, 44 | region:, 45 | user_region:, 46 | output_path:, 47 | aws_access_key_id:, 48 | aws_secret_access_key:, 49 | payloads_bucket:, 50 | target_aws_account_id:, 51 | source_aws_account_id: 52 | ) 53 | @project_scope = project_scope 54 | @external_id = external_id 55 | @run_uuid = run_uuid 56 | @user_uuid = user_uuid 57 | @region = region 58 | @user_region = user_region 59 | @output_path = output_path 60 | @aws_access_key_id = aws_access_key_id 61 | @aws_secret_access_key = aws_secret_access_key 62 | @payloads_bucket = payloads_bucket 63 | @target_aws_account_id = target_aws_account_id 64 | @source_aws_account_id = source_aws_account_id 65 | end 66 | 67 | # to hash 68 | def to_h 69 | { 70 | project_scope: @project_scope, 71 | external_id: @external_id, 72 | run_uuid: @run_uuid, 73 | user_uuid: @user_uuid, 74 | region: @region, 75 | user_region: @user_region, 76 | output_path: @output_path, 77 | aws_access_key_id: @aws_access_key_id, 78 | aws_secret_access_key: @aws_secret_access_key, 79 | payloads_bucket: @payloads_bucket, 80 | target_aws_account_id: @target_aws_account_id, 81 | source_aws_account_id: @source_aws_account_id, 82 | tmp_aws_access_key_id: @tmp_aws_access_key_id, 83 | tmp_aws_secret_access_key: @tmp_aws_secret_access_key, 84 | tmp_aws_session_token: @tmp_aws_session_token 85 | } 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/acm.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Acm 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def acm_allow_general_permissions(aws_account_id:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "acm:ListCertificates", 11 | "acm:DescribeCertificate" 12 | ], 13 | "Resource" => "*" 14 | } 15 | end 16 | 17 | # ------ 18 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/apigatewayv2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Apigatewayv2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | 5 | def apigatewayv2_allow_general_permissions(aws_account_id:,region:) 6 | { 7 | "Effect" => "Allow", 8 | "Action" => [ 9 | "apigateway:GET" 10 | ], 11 | "Resource" => "*" 12 | } 13 | end 14 | 15 | # https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/get-authorizers.html 16 | def apigatewayv2_get_authorizers(aws_account_id:,region:,api_id:nil) 17 | { 18 | "Effect" => "Allow", 19 | "Action" => "apigateway:GET", 20 | "Resource" => "arn:aws:apigateway:#{region}::/apis/#{api_id}/authorizers/*" 21 | } 22 | end 23 | 24 | # https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/get-integrations.html 25 | def apigatewayv2_get_integrations(aws_account_id:,region:,api_id:nil) 26 | { 27 | "Effect" => "Allow", 28 | "Action" => "apigateway:GET", 29 | "Resource" => "arn:aws:apigateway:#{region}::/apis/#{api_id}/integrations/*" 30 | } 31 | end 32 | 33 | 34 | # ------ 35 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/cloudformation.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Cloudformation 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def cloudformation_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "cloudformation:ListStacks" 11 | ], 12 | "Resource" => "*" 13 | } 14 | end 15 | 16 | def cloudformation_list_stack_resources(aws_account_id:,region:,stack_names: []) 17 | resources = stack_names.map do |stack_name| 18 | "arn:aws:cloudformation:#{region}:#{aws_account_id}:stack/#{stack_name}/*" 19 | end 20 | resources = "*" if resources.empty? 21 | { 22 | "Effect" => "Allow", 23 | "Action" => "cloudformation:ListStackResources", 24 | "Resource" => resources 25 | } 26 | end 27 | 28 | # ------ 29 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/cloudfront.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Cloudfront 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def cloudfront_allow_general_permissions(aws_account_id:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "cloudfront:ListDistributions", 11 | "cloudfront:GetDistribution", 12 | "cloudfront:ListInvalidations", 13 | "cloudfront:ListCloudFrontOriginAccessIdentities", 14 | "cloudfront:GetCloudFrontOriginAccessIdentity", 15 | "cloudfront:GetCloudFrontOriginAccessIdentityConfig", 16 | "cloudfront:GetOriginAccessControl", 17 | "cloudfront:ListOriginAccessControls" 18 | ], 19 | "Resource" => "*" 20 | } 21 | end 22 | 23 | # ------ 24 | end; end 25 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/codebuild.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Codebuild 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def codebuild_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "codebuild:ListProjects", 11 | "codebuild:ListBuilds", 12 | "codebuild:BatchGetProjects" 13 | ], 14 | "Resource" => "*" 15 | } 16 | end 17 | 18 | 19 | # ------ 20 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/codepipeline.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Codepipeline 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def codepipeline_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "codepipeline:ListPipelines", 11 | "codepipeline:GetPipeline" 12 | ], 13 | "Resource" => "*" 14 | } 15 | end 16 | 17 | # ------ 18 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/cognito_idp.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::CognitoIdp 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def cognito_idp_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "cognito-idp:ListUserPools", 11 | "cognito-idp:ListUserPoolClients" 12 | ], 13 | "Resource" => "*" 14 | } 15 | end 16 | 17 | 18 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/describe-user-pool.html 19 | def cognito_idp_describe_user_pool(aws_account_id:,region:) 20 | { 21 | "Effect" => "Allow", 22 | "Action" => "cognito-idp:DescribeUserPool", 23 | "Resource" => "arn:aws:cognito-idp:#{region}:#{aws_account_id}:userpool/*" 24 | } 25 | end 26 | 27 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/list-users.html 28 | def cognito_idp_list_users(aws_account_id:,region:) 29 | { 30 | "Effect" => "Allow", 31 | "Action" => "cognito-idp:ListUsers", 32 | "Resource" => "arn:aws:cognito-idp:#{region}:#{aws_account_id}:userpool/*" 33 | } 34 | end 35 | 36 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/describe-user-pool-client.html 37 | def cognito_idp_describe_user_pool_client(aws_account_id:,region:) 38 | { 39 | "Effect" => "Allow", 40 | "Action" => "cognito-idp:DescribeUserPoolClient", 41 | "Resource" => "arn:aws:cognito-idp:#{region}:#{aws_account_id}:userpool/*/client/*" 42 | } 43 | end 44 | 45 | # ------ 46 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/dynamodb.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Dynamodb 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | def dynamodb_allow_general_permissions(aws_account_id:,region:) 6 | { 7 | "Effect" => "Allow", 8 | "Action" => [ 9 | "dynamodb:ListTables", 10 | "dynamodb:DescribeTable" 11 | ], 12 | "Resource" => "*" 13 | } 14 | end 15 | 16 | # ------ 17 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/dynamodbstreams.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Dynamodbstreams 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def dynamodbstreams_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "dynamodb:ListTables", 11 | "dynamodb:DescribeTable", 12 | "dynamodb:ListStreams", 13 | "dynamodb:DescribeStream" 14 | 15 | ], 16 | "Resource" => "*" 17 | } 18 | end 19 | 20 | # ------ 21 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/ec2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Ec2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def ec2_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "ec2:DescribeVpcs", 11 | "ec2:DescribeSubnets", 12 | "ec2:DescribeRouteTables", 13 | "ec2:DescribeInternetGateways", 14 | "ec2:DescribeSecurityGroups" 15 | ], 16 | "Resource" => "*" 17 | } 18 | end 19 | 20 | # ------ 21 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/ecr.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Ecr 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | def ecr_allow_general_permissions(aws_account_id:,region:) 6 | { 7 | "Effect" => "Allow", 8 | "Action" => "ecr:DescribeRepositories", 9 | "Resource" => "*" 10 | } 11 | end 12 | 13 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/describe-images.html 14 | def ecr_describe_images(aws_account_id:,region:,repository_names:[]) 15 | resources = repository_names.map do |repo_name| 16 | "arn:aws:ecr:#{region}:#{aws_account_id}:repository/#{repo_name}" 17 | end 18 | resources = "*" if resources.empty? 19 | { 20 | "Effect" => "Allow", 21 | "Action" => "ecr:DescribeImages", 22 | "Resource" => resources 23 | } 24 | end 25 | 26 | # ------ 27 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/ecs.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Ecs 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def ecs_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "ecs:DescribeClusters", 11 | "ecs:ListServices", 12 | "ecs:DescribeTasks", 13 | "ecs:ListTasks", 14 | "ecs:ListTaskDefinitions" 15 | ], 16 | "Resource" => "*" 17 | } 18 | end 19 | 20 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/describe-services.html 21 | # services is required - a list of services to describe 22 | def ecs_describe_services(aws_account_id:,region:,cluster_name:,services:[]) 23 | resources = services.map do |name| 24 | "arn:aws:ecs:#{region}:#{aws_account_id}:service/#{cluster_name}/#{name}" 25 | end 26 | resources = "*" if resources.empty? 27 | { 28 | "Effect" => "Allow", 29 | "Action" => "ecs:DescribeServices", 30 | "Resource" => resources 31 | } 32 | end 33 | 34 | # ------ 35 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/elbv2.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Elbv2 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def elbv2_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "elasticloadbalancing:DescribeLoadBalancers", 11 | "elasticloadbalancing:DescribeListeners", 12 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 13 | "elasticloadbalancing:DescribeRules", 14 | "elasticloadbalancing:DescribeTargetGroups", 15 | "elasticloadbalancing:DescribeTargetGroupAttributes", 16 | "elasticloadbalancing:DescribeTargetHealth" 17 | ], 18 | "Resource" => "*" 19 | } 20 | end 21 | 22 | # ------ 23 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/lambda.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Lambda 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def lambda_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "lambda:GetPolicy", 11 | "lambda:ListFunctions", 12 | "lambda:ListLayers" 13 | ], 14 | "Resource" => "*" 15 | } 16 | end 17 | 18 | # https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/get-function.html 19 | def lambda_get_function(aws_account_id:,region:,function_names:[]) 20 | resources = function_names.map do |function_name| 21 | "arn:aws:lambda:#{region}:#{aws_account_id}:function:#{function_namek}" 22 | end 23 | resources = "*" if resources.empty? 24 | { 25 | "Effect" => "Allow", 26 | "Action" => "lambda:GetFunction", 27 | "Resource" => resources 28 | } 29 | end 30 | 31 | # ------ 32 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/rds.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Rds 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def rds_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "rds:DescribeDBInstances", 11 | "rds:DescribeDBSubnetGroups", 12 | "rds:DescribeDBSnapshots" 13 | ], 14 | "Resource" => "*" 15 | } 16 | end 17 | # ------ 18 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/route53.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Route53 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def route53_allow_general_permissions(aws_account_id:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "route53:GetHostedZone", 11 | "route53:ListHostedZones", 12 | "route53:ListHostedZonesByName", 13 | "route53:ListResourceRecordSets" 14 | ], 15 | "Resource" => "*" 16 | } 17 | end 18 | 19 | # ------ 20 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/s3api.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::S3api 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def s3api_allow_general_permissions(aws_account_id:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "s3:ListAllMyBuckets" 11 | ], 12 | "Resource" => "*" 13 | } 14 | end 15 | 16 | def s3api_allow_scoped_permissions(aws_account_id:,bucket_names: []) 17 | resources = bucket_names.map{|b|"arn:aws:s3:::#{b}/*"} 18 | if resources.empty? 19 | resources = "*" 20 | else 21 | bucket_names.each do |bucket| 22 | resources.push "arn:aws:s3:::#{bucket}" 23 | end 24 | end 25 | { 26 | "Effect" => "Allow", 27 | "Action" => [ 28 | "s3:HeadBucket", 29 | "s3:GetBucketNotification", 30 | "s3:GetBucketPolicy", 31 | "s3:GetBucketCors", 32 | "s3:GetBucketWebsite", 33 | "s3:GetObject", 34 | "s3:HeadObject", 35 | "s3:GetObjectAcl", 36 | "s3:GetBucketPublicAccessBlock" 37 | ], 38 | "Resource" => resources 39 | } 40 | end 41 | 42 | # ------ 43 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policies/servicediscovery.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Aws::Policies::Servicediscovery 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def servicediscovery_allow_general_permissions(aws_account_id:,region:) 7 | { 8 | "Effect" => "Allow", 9 | "Action" => [ 10 | "servicediscovery:ListServices", 11 | "servicediscovery:ListNamespaces" 12 | ], 13 | "Resource" => "*" 14 | } 15 | end 16 | # ------ 17 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/aws/policy.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'yaml' 3 | 4 | class Cpbvt::Payloads::Aws::Policy 5 | include Cpbvt::Payloads::Aws::Policies::Acm 6 | include Cpbvt::Payloads::Aws::Policies::Apigatewayv2 7 | include Cpbvt::Payloads::Aws::Policies::Cloudformation 8 | include Cpbvt::Payloads::Aws::Policies::Cloudfront 9 | include Cpbvt::Payloads::Aws::Policies::Codebuild 10 | include Cpbvt::Payloads::Aws::Policies::Codepipeline 11 | include Cpbvt::Payloads::Aws::Policies::CognitoIdp 12 | include Cpbvt::Payloads::Aws::Policies::Dynamodb 13 | include Cpbvt::Payloads::Aws::Policies::Dynamodbstreams 14 | include Cpbvt::Payloads::Aws::Policies::Ec2 15 | include Cpbvt::Payloads::Aws::Policies::Ecr 16 | include Cpbvt::Payloads::Aws::Policies::Ecs 17 | include Cpbvt::Payloads::Aws::Policies::Elbv2 18 | include Cpbvt::Payloads::Aws::Policies::Lambda 19 | include Cpbvt::Payloads::Aws::Policies::Rds 20 | include Cpbvt::Payloads::Aws::Policies::Route53 21 | include Cpbvt::Payloads::Aws::Policies::S3api 22 | include Cpbvt::Payloads::Aws::Policies::Servicediscovery 23 | 24 | def initialize 25 | @@permissions = [] 26 | end 27 | 28 | def self.add region, command, general_params, specific_params 29 | specific_params[:aws_account_id] = general_params.target_aws_account_id 30 | specific_params[:region] = region if region != 'global' 31 | 32 | permissions = self.send command, **specific_params 33 | permissions = [permissions] if permissions.is_a?(Hash) 34 | if region != 'global' 35 | permissions.map! do |permission| 36 | self.condition_region permission, region 37 | end 38 | end 39 | permissions.each do |permission| 40 | @@permissions.push permission 41 | end 42 | end 43 | 44 | def self.condition_region permission, region 45 | permission['Condition'] ||= {} 46 | permission['Condition']['StringEquals'] ||= {} 47 | permission['Condition']['StringEquals']["aws:RequestedRegion"] = region 48 | permission 49 | end 50 | 51 | def self.generate! general_params 52 | path = File.join( 53 | File.dirname(File.expand_path(__FILE__)), 54 | "cross-account-role-template.yaml" 55 | ) 56 | cfn_template = YAML.load_file(path) 57 | 58 | # Policy exceeding the 6144 characters limit can't be saved. 59 | max_count = 6000 60 | current_count = 0 61 | i = 0 62 | 63 | permissions_chunk = [] 64 | @@permissions.each_with_index do |permission,index| 65 | json = permission.to_json 66 | if (current_count + json.size) > max_count || @@permissions.size-1 == index 67 | if @@permissions.size-1 == index 68 | permissions_chunk.push permission 69 | end 70 | #add another 71 | policy_template = { 72 | "PolicyName" => "BootcampPolicy-#{general_params.run_uuid}-#{i}", 73 | "PolicyDocument" => { 74 | "Version" => "2012-10-17", 75 | "Statement" => [] 76 | } 77 | } 78 | cfn_template['Resources']['CrossAccountRole']['Properties']['Policies'].push(policy_template) 79 | cfn_template['Resources']['CrossAccountRole']['Properties']['Policies'][i]['PolicyDocument']['Statement'] = permissions_chunk 80 | 81 | i += 1 82 | current_count = 0 83 | permissions_chunk = [] 84 | else 85 | #add to existing 86 | current_count += json.size 87 | permissions_chunk.push permission 88 | end 89 | end 90 | 91 | cfn_template['Resources']['CrossAccountRole']['Properties']['RoleName'] = "Validator-#{general_params.external_id}" 92 | cfn_template['Resources']['CrossAccountRole']['Properties']['AssumeRolePolicyDocument']['Statement'][0]['Principal']['AWS'] = "arn:aws:iam::#{general_params.source_aws_account_id}:user/cloud-project-validation-tool" 93 | cfn_template['Resources']['CrossAccountRole']['Properties']['AssumeRolePolicyDocument']['Statement'][0]['Condition']['StringEquals']['sts:ExternalId'] = general_params.external_id 94 | 95 | output_path = File.join( 96 | general_params.output_path, 97 | general_params.project_scope, 98 | "user-#{general_params.user_uuid}", 99 | "#{general_params.run_uuid}-cross-account.yaml" 100 | ) 101 | 102 | dirpath = File.dirname output_path 103 | FileUtils.mkdir_p(dirpath) 104 | 105 | File.open(output_path, 'w') do |f| 106 | f.write(cfn_template.to_yaml) 107 | end 108 | 109 | object_key = Cpbvt::Uploader.object_key( 110 | user_uuid: general_params.user_uuid, 111 | project_scope: general_params.project_scope, 112 | run_uuid: general_params.run_uuid, 113 | region: general_params.region, 114 | filename: File.basename(output_path) 115 | ) 116 | Cpbvt::Uploader.run( 117 | file_path: output_path, 118 | object_key: object_key, 119 | aws_region: general_params.user_region, 120 | aws_access_key_id: general_params.aws_access_key_id, 121 | aws_secret_access_key: general_params.aws_secret_access_key, 122 | payloads_bucket: general_params.payloads_bucket 123 | ) 124 | File.delete(output_path) if File.exist?(output_path) 125 | 126 | return object_key 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/command.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | class Cpbvt::Payloads::Azure::Command 4 | include Cpbvt::Payloads::Azure::Commands::Network 5 | include Cpbvt::Payloads::Azure::Commands::Storage 6 | include Cpbvt::Payloads::Azure::Commands::VirtualMachine 7 | include Cpbvt::Payloads::Azure::Commands::FunctionApp 8 | include Cpbvt::Payloads::Azure::Commands::Group 9 | 10 | def self.login azure_client_id, azure_secret_client, azure_tenant_id, user_subscription_id 11 | command = <<~COMMAND 12 | az login --service-principal \ 13 | --username #{azure_client_id} \ 14 | --password #{azure_secret_client} \ 15 | --tenant #{azure_tenant_id} 16 | az account set --subscription #{user_subscription_id} 17 | COMMAND 18 | puts "[Executing] #{command}" 19 | 20 | begin 21 | stdout_str, exit_code = Open3.capture2(command)#, :stdin_data=>post_content) 22 | puts "\nSTDOUT_STR ==" 23 | puts stdout_str 24 | # Example output expected to be returned 25 | #[ 26 | # { 27 | # "cloudName": "AzureCloud", 28 | # "homeTenantId": "f73244ae-3e74-43eb....", 29 | # "id": "7f3352cf-6c7d-456a-8ecb....", 30 | # "isDefault": true, 31 | # "managedByTenants": [], 32 | # "name": "Azure subscription", 33 | # "state": "Enabled", 34 | # "tenantId": "f73244ae-3e74-43eb-8dbf....", 35 | # "user": { 36 | # "name": "19dc2af0-a12c-493a-8ac5....", 37 | # "type": "servicePrincipal" 38 | # } 39 | # } 40 | #] 41 | result = JSON.parse(stdout_str) 42 | #result = payload['Credentials'] 43 | # TODO - check the result 44 | rescue => e 45 | puts "[ERROR] #{e.message}" 46 | result = e.message 47 | end 48 | return result 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/commands/function_app.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Azure::Commands::FunctionApp 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | def az_function_app_list resource_group: 7 | <<~COMMAND 8 | az functionapp list --resource-group #{resource_group} 9 | COMMAND 10 | end 11 | 12 | # ------ 13 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/commands/group.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Azure::Commands::Group 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://learn.microsoft.com/en-us/cli/azure/vm?view=azure-cli-latest#az-vm-list 7 | def az_resource_group_list 8 | <<~COMMAND 9 | az group list 10 | COMMAND 11 | end 12 | 13 | # ------ 14 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/commands/network.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Azure::Commands::Network 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://learn.microsoft.com/en-us/cli/azure/vm?view=azure-cli-latest#az-vm-list 7 | def az_network_vnet_list 8 | <<~COMMAND 9 | az network vnet list 10 | COMMAND 11 | end 12 | 13 | # ------ 14 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/commands/storage.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Azure::Commands::Storage 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://learn.microsoft.com/en-us/cli/azure/storage/account?view=azure-cli-latest#az-storage-account-list 7 | def az_storage_account_list 8 | <<~COMMAND 9 | az storage account list 10 | COMMAND 11 | end 12 | 13 | # https://learn.microsoft.com/en-us/cli/azure/storage/container?view=azure-cli-latest#az-storage-container-list 14 | def az_storage_container_list account_name: 15 | <<~COMMAND 16 | az storage container list \ 17 | --auth-mode login \ 18 | --account-name #{account_name} 19 | COMMAND 20 | end 21 | 22 | # https://learn.microsoft.com/en-us/cli/azure/storage/blob?view=azure-cli-latest#az-storage-blob-exists 23 | def az_storage_blob_exists account_name:, container_name:, blob_name: 24 | <<~COMMAND 25 | az storage blob exists \ 26 | --account-name #{account_name} \ 27 | --container-name #{container_name} \ 28 | --auth-mode login \ 29 | --name #{blob_name} 30 | COMMAND 31 | end 32 | 33 | 34 | # ------ 35 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/commands/virtual_machine.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Azure::Commands::VirtualMachine 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://learn.microsoft.com/en-us/cli/azure/vm?view=azure-cli-latest#az-vm-list 7 | def az_virtual_machine_list 8 | <<~COMMAND 9 | az vm list 10 | COMMAND 11 | end 12 | 13 | # ------ 14 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/general_params.rb: -------------------------------------------------------------------------------- 1 | require 'active_model' 2 | class Cpbvt::Payloads::Azure::GeneralParams 3 | include ActiveModel::Validations 4 | 5 | # source_aws_account_id = the account that we will upload payload data 6 | attr_accessor :project_scope, 7 | :user_uuid, 8 | :run_uuid, 9 | :region, 10 | :output_path, 11 | :aws_access_key_id, 12 | :aws_secret_access_key, 13 | :azure_client_id, 14 | :azure_tenant_id, 15 | :azure_client_secret, 16 | :tmp_aws_session_token, 17 | :payloads_bucket, 18 | :target_subscription_id, 19 | :target_resource_group, 20 | :source_aws_account_id, 21 | :external_id 22 | 23 | # we aren't validating the session_token since 24 | # will pul it after validation 25 | 26 | validates :external_id, presence: true 27 | validates :project_scope, presence: true 28 | validates :user_uuid , presence: true 29 | validates :run_uuid, presence: true 30 | validates :region, presence: true 31 | validates :output_path, presence: true 32 | validates :aws_access_key_id, presence: true 33 | validates :aws_secret_access_key, presence: true 34 | 35 | validates :azure_client_id, presence: true 36 | validates :azure_tenant_id, presence: true 37 | validates :azure_client_secret, presence: true 38 | 39 | validates :target_subscription_id, presence: true 40 | validates :target_resource_group, presence: true 41 | 42 | validates :payloads_bucket, presence: true 43 | validates :source_aws_account_id, presence: true 44 | 45 | def initialize( 46 | project_scope:, 47 | user_uuid:, 48 | run_uuid:, 49 | region:, 50 | azure_client_id:, 51 | azure_tenant_id:, 52 | azure_client_secret:, 53 | output_path:, 54 | aws_access_key_id:, 55 | aws_secret_access_key:, 56 | payloads_bucket:, 57 | source_aws_account_id:, 58 | target_subscription_id:, 59 | target_resource_group:, 60 | external_id: 61 | ) 62 | @project_scope = project_scope 63 | @run_uuid = run_uuid 64 | @user_uuid = user_uuid 65 | @region = region 66 | @azure_client_id = azure_client_id 67 | @azure_tenant_id = azure_tenant_id 68 | @azure_client_secret = azure_client_secret 69 | @output_path = output_path 70 | @aws_access_key_id = aws_access_key_id 71 | @aws_secret_access_key = aws_secret_access_key 72 | @payloads_bucket = payloads_bucket 73 | @source_aws_account_id = source_aws_account_id 74 | @target_subscription_id = target_subscription_id 75 | @target_resource_group = target_resource_group 76 | @external_id = external_id 77 | end 78 | 79 | # to hash 80 | def to_h 81 | { 82 | project_scope: @project_scope, 83 | run_uuid: @run_uuid, 84 | user_uuid: @user_uuid, 85 | region: @region, 86 | azure_client_id: @azure_client_id, 87 | azure_tenant_id: @azure_tenant_id, 88 | azure_client_secret: @azure_client_secret, 89 | output_path: @output_path, 90 | aws_access_key_id: @aws_access_key_id, 91 | aws_secret_access_key: @aws_secret_access_key, 92 | payloads_bucket: @payloads_bucket, 93 | source_aws_account_id: @source_aws_account_id, 94 | target_subscription_id: @target_subscription_id, 95 | target_resource_group: @target_resource_group, 96 | external_id: @external_id 97 | } 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/lighthouse-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "mspOfferName": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Specify a unique name for your offer" 9 | }, 10 | "defaultValue": "ExamPro - Cloud Project Validation Tool" 11 | }, 12 | "mspOfferDescription": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Name of the Managed Service Provider offering" 16 | }, 17 | "defaultValue": "ExamPro - Azure Account Validator" 18 | }, 19 | "managedByTenantId": { 20 | "type": "string", 21 | "metadata": { 22 | "description": "Specify the tenant id of the Managed Service Provider" 23 | }, 24 | "defaultValue": "f73244ae-3e74-43eb-8dbf-c66edefbb313" 25 | }, 26 | "authorizations": { 27 | "type": "array", 28 | "metadata": { 29 | "description": "ExamPro - Cloud Project Validation Tool Security Group - Reader" 30 | }, 31 | "defaultValue": [ 32 | { 33 | "principalId": "23599e52-e874-4ee9-ae23-427ae0839415", 34 | "roleDefinitionId": "acdd72a7-3385-48ef-bd42-f606fba81ae7", 35 | "principalIdDisplayName": "ExamPro - Cloud Project Validation Tool" 36 | } 37 | ] 38 | }, 39 | "rgName": { 40 | "type": "string", 41 | "metadata": { 42 | "description": "The name of the resource group inside the customer account for the validator to have access to." 43 | }, 44 | "defaultValue": "${UserRgName}" 45 | } 46 | }, 47 | "variables": { 48 | "mspRegistrationName": "[guid(parameters('mspOfferName'))]", 49 | "mspAssignmentName": "[guid(parameters('mspOfferName'))]" 50 | }, 51 | "resources": [ 52 | { 53 | "type": "Microsoft.ManagedServices/registrationDefinitions", 54 | "apiVersion": "2019-06-01", 55 | "name": "[variables('mspRegistrationName')]", 56 | "properties": { 57 | "registrationDefinitionName": "[parameters('mspOfferName')]", 58 | "description": "[parameters('mspOfferDescription')]", 59 | "managedByTenantId": "[parameters('managedByTenantId')]", 60 | "authorizations": "[parameters('authorizations')]" 61 | } 62 | }, 63 | { 64 | "type": "Microsoft.Resources/deployments", 65 | "apiVersion": "2018-05-01", 66 | "name": "rgAssignment", 67 | "resourceGroup": "[parameters('rgName')]", 68 | "dependsOn": [ 69 | "[resourceId('Microsoft.ManagedServices/registrationDefinitions/', variables('mspRegistrationName'))]" 70 | ], 71 | "properties":{ 72 | "mode":"Incremental", 73 | "template":{ 74 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 75 | "contentVersion": "1.0.0.0", 76 | "parameters": {}, 77 | "resources": [ 78 | { 79 | "type": "Microsoft.ManagedServices/registrationAssignments", 80 | "apiVersion": "2019-06-01", 81 | "name": "[variables('mspAssignmentName')]", 82 | "properties": { 83 | "registrationDefinitionId": "[resourceId('Microsoft.ManagedServices/registrationDefinitions/', variables('mspRegistrationName'))]" 84 | } 85 | } 86 | ] 87 | } 88 | } 89 | } 90 | ], 91 | "outputs": { 92 | "mspOfferName": { 93 | "type": "string", 94 | "value": "[concat('Managed by', ' ', parameters('mspOfferName'))]" 95 | }, 96 | "authorizations": { 97 | "type": "array", 98 | "value": "[parameters('authorizations')]" 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/policy.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'json' 3 | 4 | class Cpbvt::Payloads::Azure::Policy 5 | # permissions are delegated at the resource group level 6 | # only Reader role permissions are delegated by default 7 | def self.generate! general_params 8 | path = File.join( 9 | File.dirname(File.expand_path(__FILE__)), 10 | "lighthouse-template.json" 11 | ) 12 | 13 | file = File.read(path) 14 | arm_template = JSON.parse(file) 15 | arm_template['parameters']['rgName']['defaultValue'] = "#{general_params.target_resource_group}" 16 | 17 | output_path = File.join( 18 | general_params.output_path, 19 | general_params.project_scope, 20 | "user-#{general_params.user_uuid}", 21 | "#{general_params.run_uuid}-lighthouse-template.json" 22 | ) 23 | 24 | dirpath = File.dirname output_path 25 | FileUtils.mkdir_p(dirpath) 26 | 27 | File.open(output_path, 'w') do |f| 28 | f.write(arm_template.to_json) 29 | end 30 | 31 | object_key = Cpbvt::Uploader.object_key( 32 | user_uuid: general_params.user_uuid, 33 | project_scope: general_params.project_scope, 34 | run_uuid: general_params.run_uuid, 35 | region: general_params.region, 36 | filename: File.basename(output_path) 37 | ) 38 | Cpbvt::Uploader.run( 39 | file_path: output_path, 40 | object_key: object_key, 41 | aws_region: general_params.region, 42 | aws_access_key_id: general_params.aws_access_key_id, 43 | aws_secret_access_key: general_params.aws_secret_access_key, 44 | payloads_bucket: general_params.payloads_bucket, 45 | public_read: true 46 | ) 47 | File.delete(output_path) if File.exist?(output_path) 48 | 49 | return object_key 50 | end 51 | end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/azure/runner.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'ostruct' 3 | require 'time' 4 | require 'open3' 5 | 6 | module Cpbvt::Payloads::Azure::Runner 7 | def self.run command, general_params, specific_params={} 8 | starts_at = Time.now.to_f 9 | 10 | # if no filename provided base it on the provided command 11 | unless general_params[:filename] 12 | general_params[:filename] = "#{command.gsub('_','-')}.json" 13 | end 14 | general_params = OpenStruct.new general_params 15 | 16 | output_file = Cpbvt::Payloads::Azure::Runner::output_file( 17 | run_uuid: general_params.run_uuid, 18 | user_uuid: general_params.user_uuid, 19 | project_scope: general_params.project_scope, 20 | output_path: general_params.output_path, 21 | filename: general_params.filename 22 | ) 23 | 24 | command = Cpbvt::Payloads::Azure::Command.send(command, **specific_params) 25 | command = command.strip.gsub("\n", " ") 26 | #command = "#{command} --region #{general_params.user_region}" unless general_params.user_region == 'global' 27 | command = "#{command} -o json > #{output_file}" 28 | 29 | #stdout_str should always result in empty 30 | #string since its writing to a file 31 | stdout_str, stderr_str, status = 32 | Cpbvt::Payloads::Azure::Runner.execute( 33 | general_params.run_uuid, 34 | general_params.azure_client_id, 35 | general_params.azure_tenant_id, 36 | general_params.azure_client_secret, 37 | command 38 | ) 39 | 40 | puts stdout_str 41 | 42 | id = general_params.filename.sub(".json","") 43 | 44 | error = false 45 | error = stderr_str.strip if stderr_str != "" 46 | if error == false && File.size?(output_file).nil? 47 | error = "No data returned" 48 | end 49 | 50 | ends_at = Time.now.to_f 51 | return { 52 | id: id, 53 | params: specific_params, 54 | benchmark: { 55 | starts_at: starts_at, 56 | ends_at: ends_at, 57 | duration_in_ms: ((ends_at - starts_at)*1000).to_i 58 | }, 59 | error: error, 60 | command: command, 61 | output_file: output_file 62 | } 63 | end 64 | 65 | # create the path to where the json file will be downloaded 66 | def self.output_file(user_uuid:,run_uuid:,project_scope:,output_path:,filename:) 67 | value = File.join( 68 | output_path, 69 | project_scope, 70 | "user-#{user_uuid}", 71 | "run-#{run_uuid}", 72 | filename 73 | ) 74 | 75 | # create the folder for the downloaded json if it doesn't exist 76 | FileUtils.mkdir_p File.dirname(value) 77 | 78 | # print the desination of the outputed json 79 | #puts "[Output File] #{value}" 80 | 81 | return value 82 | end 83 | 84 | def self.execute run_uuid, azure_client_id, azure_tenant_id, azure_client_secret, command 85 | if azure_client_id.nil? || azure_tenant_id.nil? || azure_client_secret.nil? 86 | raise 'creds are nil' 87 | end 88 | # print the command so we know what is running 89 | puts "[Executing] #{command}" 90 | # run the command which will download the json 91 | 92 | self.broadcast run_uuid, command 93 | 94 | env_vars = { 95 | "AZURE_CLIENT_ID" => azure_client_id, 96 | "AZURE_TENANT_ID" => azure_tenant_id, 97 | "AZURE_CLIENT_SECRET" => azure_client_secret 98 | } 99 | # capture3 returns the follwoing --- 100 | # stdout_str, stderr_str, status 101 | Open3.capture3(env_vars, command) 102 | end 103 | 104 | def self.broadcast run_uuid, command 105 | #OVERRIDE 106 | end 107 | end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/gcp/command.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | class Cpbvt::Payloads::Gcp::Command 4 | include Cpbvt::Payloads::Gcp::Commands::Storage 5 | 6 | def self.login gcp_key_file 7 | command = <<~COMMAND 8 | gcloud auth activate-service-account \ 9 | --key-file=#{gcp_key_file} \ 10 | --format=json 11 | COMMAND 12 | puts "[Executing] #{command}" 13 | begin 14 | stdout_str, exit_code = Open3.capture2(command)#, :stdin_data=>post_content) 15 | result = JSON.parse(stdout_str) 16 | rescue => e 17 | puts "[ERROR] #{e.message}" 18 | result = e.message 19 | end 20 | return result 21 | end 22 | end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/gcp/commands/storage.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt::Payloads::Gcp::Commands::Storage 2 | def self.included base; base.extend ClassMethods; end 3 | module ClassMethods 4 | # ------ 5 | 6 | # https://cloud.google.com/sdk/gcloud/reference/storage/buckets/list 7 | def gcloud_storage_buckets_list 8 | <<~COMMAND 9 | gcloud storage buckets list 10 | COMMAND 11 | end 12 | 13 | # https://cloud.google.com/sdk/gcloud/reference/storage/objects/describe 14 | def gcloud_storage_objects_describe bucket_name:, object_name: 15 | <<~COMMAND 16 | gcloud storage objects describe \ 17 | gs://#{bucket_name}/#{object_name} 18 | COMMAND 19 | end 20 | # ------ 21 | end; end -------------------------------------------------------------------------------- /lib/cpbvt/payloads/gcp/general_params.rb: -------------------------------------------------------------------------------- 1 | require 'active_model' 2 | class Cpbvt::Payloads::Gcp::GeneralParams 3 | include ActiveModel::Validations 4 | 5 | # source_aws_account_id = the account that we will upload payload data 6 | attr_accessor :project_scope, 7 | :user_uuid, 8 | :run_uuid, 9 | :region, 10 | :output_path, 11 | :aws_access_key_id, 12 | :aws_secret_access_key, 13 | :gcp_key_file, 14 | :gcp_project_id, 15 | :tmp_aws_session_token, 16 | :payloads_bucket, 17 | :source_aws_account_id 18 | 19 | # we aren't validating the session_token since 20 | # will pul it after validation 21 | 22 | validates :project_scope, presence: true 23 | validates :user_uuid , presence: true 24 | validates :run_uuid, presence: true 25 | validates :region, presence: true 26 | validates :output_path, presence: true 27 | validates :aws_access_key_id, presence: true 28 | validates :aws_secret_access_key, presence: true 29 | validates :gcp_key_file, presence: true 30 | validates :gcp_project_id, presence: true 31 | validates :payloads_bucket, presence: true 32 | validates :source_aws_account_id, presence: true 33 | 34 | def initialize( 35 | project_scope:, 36 | user_uuid:, 37 | run_uuid:, 38 | region:, 39 | gcp_key_file:, 40 | gcp_project_id:, 41 | output_path:, 42 | aws_access_key_id:, 43 | aws_secret_access_key:, 44 | payloads_bucket:, 45 | source_aws_account_id: 46 | ) 47 | @project_scope = project_scope 48 | @run_uuid = run_uuid 49 | @user_uuid = user_uuid 50 | @region = region 51 | @gcp_key_file = gcp_key_file 52 | @gcp_project_id = gcp_project_id 53 | @output_path = output_path 54 | @aws_access_key_id = aws_access_key_id 55 | @aws_secret_access_key = aws_secret_access_key 56 | @payloads_bucket = payloads_bucket 57 | @source_aws_account_id = source_aws_account_id 58 | end 59 | 60 | # to hash 61 | def to_h 62 | { 63 | project_scope: @project_scope, 64 | run_uuid: @run_uuid, 65 | user_uuid: @user_uuid, 66 | region: @region, 67 | gcp_key_file: @gcp_key_file, 68 | gcp_project_id: @gcp_project_id, 69 | output_path: @output_path, 70 | aws_access_key_id: @aws_access_key_id, 71 | aws_secret_access_key: @aws_secret_access_key, 72 | payloads_bucket: @payloads_bucket, 73 | source_aws_account_id: @source_aws_account_id, 74 | } 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/cpbvt/payloads/gcp/runner.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'ostruct' 3 | require 'time' 4 | require 'open3' 5 | 6 | module Cpbvt::Payloads::Gcp::Runner 7 | def self.run command, general_params, specific_params={} 8 | starts_at = Time.now.to_f 9 | 10 | # if no filename provided base it on the provided command 11 | unless general_params[:filename] 12 | general_params[:filename] = "#{command.gsub('_','-')}.json" 13 | end 14 | general_params = OpenStruct.new general_params 15 | 16 | output_file = Cpbvt::Payloads::Gcp::Runner::output_file( 17 | run_uuid: general_params.run_uuid, 18 | user_uuid: general_params.user_uuid, 19 | project_scope: general_params.project_scope, 20 | gcp_project_id: general_params.gcp_project_id, 21 | output_path: general_params.output_path, 22 | filename: general_params.filename 23 | ) 24 | 25 | command = Cpbvt::Payloads::Gcp::Command.send(command, **specific_params) 26 | command = command.strip.gsub("\n", " ") 27 | command = "#{command} --project #{general_params.gcp_project_id}" 28 | command = "#{command} --format json > #{output_file}" 29 | 30 | #stdout_str should always result in empty 31 | #string since its writing to a file 32 | stdout_str, stderr_str, status = 33 | Cpbvt::Payloads::Gcp::Runner.execute( 34 | general_params.run_uuid, 35 | command, 36 | general_params.gcp_key_file 37 | ) 38 | 39 | puts stdout_str 40 | 41 | id = general_params.filename.sub(".json","") 42 | 43 | error = false 44 | error = stderr_str.strip if stderr_str != "" 45 | if error == false && File.size?(output_file).nil? 46 | error = "No data returned" 47 | end 48 | 49 | 50 | error = false 51 | error = stderr_str.strip if stderr_str != "" 52 | if error == false && File.size?(output_file).nil? 53 | error = "No data returned" 54 | end 55 | 56 | ends_at = Time.now.to_f 57 | return { 58 | id: id, 59 | params: specific_params, 60 | benchmark: { 61 | starts_at: starts_at, 62 | ends_at: ends_at, 63 | duration_in_ms: ((ends_at - starts_at)*1000).to_i 64 | }, 65 | error: error, 66 | command: command, 67 | output_file: output_file 68 | } 69 | end 70 | 71 | # create the path to where the json file will be downloaded 72 | def self.output_file(user_uuid:,run_uuid:,project_scope:,gcp_project_id:,output_path:,filename:) 73 | value = File.join( 74 | output_path, 75 | project_scope, 76 | "user-#{user_uuid}", 77 | "run-#{run_uuid}", 78 | gcp_project_id, 79 | filename 80 | ) 81 | 82 | # create the folder for the downloaded json if it doesn't exist 83 | FileUtils.mkdir_p File.dirname(value) 84 | 85 | # print the desination of the outputed json 86 | #puts "[Output File] #{value}" 87 | 88 | return value 89 | end 90 | 91 | def self.execute run_uuid, command, gcp_key_file 92 | # print the command so we know what is running 93 | puts "[Executing] #{command}" 94 | # run the command which will download the json 95 | 96 | self.broadcast run_uuid, command 97 | 98 | env_vars = { 99 | 'GOOGLE_APPLICATION_CREDENTIALS' => gcp_key_file 100 | } 101 | 102 | # capture3 returns the follwoing --- 103 | # stdout_str, stderr_str, status 104 | Open3.capture3(env_vars, command) 105 | end 106 | 107 | def self.broadcast run_uuid, command 108 | #OVERRIDE 109 | end 110 | end -------------------------------------------------------------------------------- /lib/cpbvt/tester/Readme.md: -------------------------------------------------------------------------------- 1 | There are three levels: 2 | - Describe a grouping of tests 3 | - Test a grouping of asserts 4 | - Asserts individual unit tests -------------------------------------------------------------------------------- /lib/cpbvt/tester/assert_cfn_resource.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Tester::AssertCfnResource 2 | def initialize( 3 | describe_key:, 4 | spec_key:, 5 | report:, 6 | manifest:, 7 | stack_name:, 8 | resource_type: 9 | ) 10 | @describe_key = describe_key 11 | @spec_key = spec_key 12 | @report = report 13 | 14 | begin 15 | cfn_stacks = manifest.get_output!('cloudformation-list-stacks') 16 | rescue Errno::ENOENT 17 | self.fail! kind: 'assert_cfn_resource', message: 'File not found', data: {key: 'cloudformation-list-stacks'} 18 | rescue Errno::EACCES 19 | self.fail! kind: 'assert_cfn_resource', message: 'access denied', data: {key: 'cloudformation-list-stacks'} 20 | end # begin 21 | 22 | if cfn_stacks['StackSummaries'] 23 | cicd_stack = cfn_stacks['StackSummaries'].find do |stack| 24 | stack['StackName'] == stack_name 25 | end 26 | end 27 | if cicd_stack 28 | self.pass!( 29 | kind: 'assert_cfn_resource:find', 30 | message: 'found value to match key', 31 | data: { 32 | key: 'StackName', 33 | expected_value: stack_name 34 | }) 35 | else 36 | if cfn_stacks['StackSummaries'] 37 | values = cfn_stacks['StackSummaries'].map{|t| t['StackName'] } 38 | else 39 | values = [] 40 | end 41 | self.fail!( 42 | kind: 'assert_cfn_resource:find', 43 | message: 'failed to find value to match key', 44 | data: { 45 | key: 'StackName', 46 | expected_value: stack_name, 47 | found_values: values 48 | }) 49 | end 50 | 51 | # extract the stack id 52 | cicd_stack_id = cicd_stack['StackId'].split('/').last 53 | 54 | begin 55 | cicd_stack_resources = manifest.get_output!("cloudformation-list-stack-resources__#{cicd_stack_id}") 56 | rescue Errno::ENOENT 57 | self.fail! kind: 'assert_cfn_resource', message: 'File not found', data: {key: "cloudformation-list-stack-resources__#{cicd_stack_id}"} 58 | rescue Errno::EACCES 59 | self.fail! kind: 'assert_cfn_resource', message: 'access denied', data: {key: "cloudformation-list-stack-resources__#{cicd_stack_id}"} 60 | end # begin 61 | 62 | @data = cicd_stack_resources['StackResourceSummaries'].find do |resource| 63 | resource["ResourceType"] == resource_type 64 | end 65 | if @data 66 | self.pass!( 67 | kind: 'assert_cfn_resource:find', 68 | message: 'found value to match key', 69 | data: { 70 | key: 'ResourceType', 71 | expected_value: resource_type 72 | }) 73 | else 74 | values = cicd_stack_resources['StackResourceSummaries'].map{|t| t['ResourceType'] } 75 | self.fail!( 76 | kind: 'assert_cfn_resource:find', 77 | message: 'failed to find value to match key', 78 | data: { 79 | key: 'ResourceType', 80 | expected_value: resource_type, 81 | found_values: values 82 | }) 83 | end 84 | end 85 | 86 | def returns key 87 | data = @data 88 | if key == :all || key.nil? 89 | self.pass! kind: 'asset_cfn_resource:returns', message: 'return all data' 90 | return data 91 | end 92 | if data.key?(key) 93 | self.pass! kind: 'asset_cfn_resource:returns', message: 'return all data with provided key', data: { 94 | provided_key: key 95 | } 96 | return data[key] 97 | else 98 | self.fail! kind: 'asset_cfn_resource:returns', message: 'failed to return data with provided key since key does not exist', data: { 99 | provided_key: key 100 | } 101 | end 102 | end 103 | 104 | def pass! kind:, message:, data: {} 105 | @report.pass!( 106 | describe_key: @describe_key, 107 | spec_key: @spec_key, 108 | kind: kind, 109 | message: message, 110 | data: data 111 | ) 112 | end 113 | 114 | def fail! kind:, message:, data: {} 115 | @report.fail!( 116 | describe_key: @describe_key, 117 | spec_key: @spec_key, 118 | kind: kind, 119 | message: message, 120 | data: data 121 | ) 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/cpbvt/tester/assert_json.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Tester::AssertJson 2 | def initialize(describe_key:, spec_key:, report:,data:, keys:) 3 | unless data.is_a?(Hash) 4 | raise "expected Hash but got #{data.class.to_s}" 5 | end 6 | @json_path = [] 7 | @describe_key = describe_key 8 | @spec_key = spec_key 9 | @report = report 10 | value = data 11 | keys.each do |key| 12 | if value.key?(key) 13 | @json_path.push key 14 | value = value[key] 15 | else 16 | value = nil 17 | break 18 | end 19 | end 20 | @json_path = @json_path.join('.') 21 | @value = value 22 | return self 23 | end 24 | 25 | def expects_true 26 | kind = "assert_json:expects_true" 27 | data_payload = {} 28 | if @value == true 29 | self.pass!(kind: kind, data: data_payload, message: 'value was found to be true') 30 | else 31 | self.fail!(kind: kind, data: data_payload, message: 'value was not found to be true') 32 | end 33 | return self 34 | end 35 | 36 | def expects_false 37 | kind = "assert_json:expects_false" 38 | data_payload = {} 39 | if @value == false 40 | self.pass!(kind: kind, data: data_payload, message: 'value was equal found to be false') 41 | else 42 | self.fail!(kind: kind, data: data_payload, message: 'value was not found to be false') 43 | end 44 | return self 45 | end 46 | 47 | def expects_start_with value 48 | kind = "assert_json:expects_start_with" 49 | data_payload = { 50 | expected_start_with: value 51 | } 52 | if @value.start_with?(value) 53 | self.pass!(kind: kind, data: data_payload, message: 'value was found to start with') 54 | else 55 | self.fail!(kind: kind, data: data_payload, message: 'value did not start with') 56 | end 57 | return self 58 | end 59 | 60 | def expects_end_with value 61 | kind = "assert_json:expects_end_with" 62 | data_payload = { 63 | expected_end_with: value 64 | } 65 | if @value.end_with?(value) 66 | self.pass!(kind: kind, data: data_payload, message: 'value was found to end with') 67 | else 68 | self.fail!(kind: kind, data: data_payload, message: 'value did not end with') 69 | end 70 | return self 71 | end 72 | 73 | def expects_eq value 74 | if @value == value 75 | self.pass!( 76 | kind: 'assert_json:expects_eq', 77 | message: 'value was equal to', 78 | data: { 79 | provided_value: value, 80 | actual_value: @value, 81 | json_path: @json_path 82 | } 83 | ) 84 | else 85 | self.fail!( 86 | kind: 'assert_json:expects_eq', 87 | message: 'value was not equal to', 88 | data: { 89 | provided_value: value, 90 | actual_value: @value, 91 | json_path: @json_path 92 | } 93 | ) 94 | end 95 | return self 96 | end 97 | 98 | def expects_gt value 99 | if @value.is_a?(Numeric) && @value > value 100 | self.pass!( 101 | kind: 'assert_json:expects_gt', 102 | message: 'value was greater than', 103 | data: { 104 | provided_value: value, 105 | actual_value: @value, 106 | json_path: @json_path 107 | } 108 | ) 109 | else 110 | self.fail!( 111 | kind: 'assert_json:expects_match', 112 | message: 'value was not greater than', 113 | data: { 114 | provided_value: value, 115 | actual_value: @value, 116 | json_path: @json_path 117 | } 118 | ) 119 | end 120 | return self 121 | end 122 | 123 | def expects_match value 124 | if @value.is_a?(String) && @value.match(value) 125 | self.pass!( 126 | kind: 'assert_json:expects_match', 127 | message: 'matched as expected', 128 | data: { 129 | provided_value: value, 130 | actual_value: @value, 131 | json_path: @json_path 132 | } 133 | ) 134 | else 135 | self.fail!( 136 | kind: 'assert_json:expects_match', 137 | message: 'failed to match', 138 | data: { 139 | provided_value: value, 140 | actual_value: @value, 141 | json_path: @json_path 142 | } 143 | ) 144 | end 145 | return self 146 | end 147 | 148 | def expects_not_nil 149 | unless @value.nil? 150 | self.pass!( 151 | kind: 'assert_json:expects_not_nil', 152 | message: 'value was found to be not nil', 153 | data: { 154 | actual_value: @value, 155 | json_path: @json_path 156 | } 157 | ) 158 | else 159 | self.fail!( 160 | kind: 'assert_json:expects_not_nil', 161 | message: 'failed since value is nil', 162 | data: { 163 | actual_value: @value, 164 | json_path: @json_path 165 | } 166 | ) 167 | end 168 | return self 169 | end 170 | 171 | def returns key 172 | data = @value 173 | if key == :all || key.nil? 174 | self.pass! kind: 'assert_json:returns', message: 'return all data', data: { 175 | json_path: @json_path 176 | } 177 | return data 178 | end 179 | if key == :first 180 | if data.is_a?(Array) && data.count > 0 181 | self.pass!( 182 | kind: 'assert_json:returns', 183 | message: 'returns first record' 184 | ) 185 | return data.first 186 | else 187 | self.fail!( 188 | kind: 'assert_json:returns', 189 | message: 'failed to return first record' 190 | ) 191 | end 192 | end 193 | if data.key?(key) 194 | self.pass! kind: 'assert_json:returns', message: 'return all data with provided key', data: { 195 | provided_key: key, 196 | json_path: @json_path + ".#{key}" 197 | } 198 | return data[key] 199 | else 200 | self.fail! kind: 'assert_json:returns', message: 'failed to return data with provided key since key does not exist', data: { 201 | provided_key: key , 202 | json_path: @json_path + ".#{key}" 203 | } 204 | end 205 | end 206 | 207 | def pass! kind:, message:, data: {} 208 | @report.pass!( 209 | describe_key: @describe_key, 210 | spec_key: @spec_key, 211 | kind: kind, 212 | message: message, 213 | data: data 214 | ) 215 | end 216 | 217 | def fail! kind:, message:, data: {} 218 | @report.fail!( 219 | describe_key: @describe_key, 220 | spec_key: @spec_key, 221 | kind: kind, 222 | message: message, 223 | data: data 224 | ) 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /lib/cpbvt/tester/assert_load.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Tester::AssertLoad 2 | # key (string) - 3 | # AWS often returns data with a single property 4 | # so for arrays we often will want to find a record 5 | # and need to specific the key that we'll be looking 6 | # at for data 7 | # eg 8 | # { "UserPools" => {} } 9 | 10 | # GCP can return data an array or an object. 11 | def initialize( 12 | describe_key:, 13 | spec_key:, 14 | report:, 15 | manifest:, 16 | manifest_payload_key:, 17 | key: nil 18 | ) 19 | @report = report 20 | @describe_key = describe_key 21 | @spec_key = spec_key 22 | @data_raw = nil 23 | 24 | # load data 25 | begin 26 | @data_raw = manifest.get_output(manifest_payload_key) 27 | rescue Errno::ENOENT 28 | self.fail! kind: 'load_data', message: 'File not found', data: {key: manifest_payload_key} 29 | rescue Errno::EACCES 30 | self.fail! kind: 'load_data', message: 'access denied', data: {key: manifest_payload_key} 31 | end # begin 32 | 33 | if @data_raw.is_a?(Array) 34 | self.pass! kind: 'load_data', message: 'loaded array data from manifest', data: { key: manifest_payload_key } 35 | elsif @data_raw.is_a?(Hash) 36 | self.pass! kind: 'load_data', message: 'loaded object data from manifest', data: { key: manifest_payload_key } 37 | if key 38 | if @data_raw.key?(key) 39 | @data_first_filter = @data_raw[key] 40 | if @data_first_filter.nil? 41 | self.fail! kind: 'load_data:first_filter', message: "first filter data returned nil", data: { key: key } 42 | else 43 | self.pass! kind: 'load_data:first_filter', message: "first filter data returned data", data: { key: key } 44 | return self 45 | end 46 | else 47 | self.fail! kind: 'load_data:first_filter', message: "first filter data key does not exists", data: { key: key } 48 | end # if @data_raw.key? 49 | end # if key 50 | elsif @data_raw.nil? 51 | self.fail! kind: 'load_data', message: 'loaded data from manifest and nil was found', data: {key: manifest_payload_key } 52 | else 53 | self.fail! kind: 'load_data', message: 'payload key found in manifest file', data: {key: manifest_payload_key} 54 | end # if @data_raw.is_a? 55 | 56 | end # def initialize 57 | 58 | def find key, value 59 | data = @data_first_filter || @data_raw 60 | @data_found = data.find{|t| t[key] == value} 61 | if @data_found 62 | self.pass! kind: 'load_data:find', message: 'found value to match key', data: { 63 | key: key, 64 | expected_value: value 65 | } 66 | return self 67 | else 68 | values = data.map{|t| t[key] } 69 | self.fail! kind: 'load_data:find', message: 'failed to find value to match key', data: { 70 | key: key, 71 | expected_value: value, 72 | found_values: values 73 | } 74 | end 75 | end 76 | 77 | def returns key 78 | data = @data_found || @data_first_filter || @data_raw 79 | if key == :all || key.nil? 80 | self.pass!( 81 | kind: 'load_data:returns', 82 | message: 'return all data' 83 | ) 84 | return data 85 | end 86 | if key == :first 87 | if data.is_a?(Array) && data.count > 0 88 | self.pass!( 89 | kind: 'load_data:returns', 90 | message: 'returns first record' 91 | ) 92 | return data.first 93 | else 94 | self.fail!( 95 | kind: 'load_data:returns', 96 | message: 'failed to return first record' 97 | ) 98 | end 99 | end 100 | if data.key?(key) 101 | self.pass!( 102 | kind: 'load_data:returns', 103 | message: 'return all data with provided key', 104 | data: { 105 | provided_key: key 106 | }) 107 | return data[key] 108 | else 109 | self.fail!( 110 | kind: 'load_data:returns', 111 | message: 'failed to return data with provided key since key does not exist', 112 | data: { 113 | provided_key: key 114 | }) 115 | end 116 | end 117 | 118 | def pass! kind:, message:, data: {} 119 | @report.pass!( 120 | describe_key: @describe_key, 121 | spec_key: @spec_key, 122 | kind: kind, 123 | message: message, 124 | data: data 125 | ) 126 | end 127 | 128 | def fail! kind:, message:, data: {} 129 | @report.fail!( 130 | describe_key: @describe_key, 131 | spec_key: @spec_key, 132 | kind: kind, 133 | message: message, 134 | data: data 135 | ) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/cpbvt/tester/assert_select.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Tester::AssertSelect 2 | def initialize(describe_key:, spec_key:, report:, data:, context:) 3 | @describe_key = describe_key 4 | @spec_key = spec_key 5 | @report = report 6 | @data = data 7 | @context = context 8 | 9 | self.evaluate 10 | end 11 | 12 | def evaluate 13 | @report.iter_start!( 14 | describe_key: @describe_key, 15 | spec_key: @spec_key, 16 | kind: 'assert_select' 17 | ) 18 | 19 | found = 20 | @data.select do |data| 21 | #@self_before_instance_eval = eval("self", @context.binding) 22 | #instance_eval &@context, data 23 | @report.iter_add!( 24 | describe_key: @describe_key, 25 | spec_key: @spec_key 26 | ) 27 | @context.call(self,data) 28 | 29 | @report.iter_last(@describe_key,@spec_key).all?{|t| t[:status] == 'pass'} 30 | end 31 | 32 | if found.any? 33 | status = 'pass' 34 | @found_data = found 35 | else 36 | status = 'fail' 37 | end 38 | 39 | @report.iter_end!( 40 | describe_key: @describe_key, 41 | spec_key: @spec_key, 42 | status: status 43 | ) 44 | end 45 | 46 | def expects_any? data, key, label: nil, &block 47 | kind = 48 | if label 49 | "assert_select[#{self.iter_index}]:expects_any?:#{label}" 50 | else 51 | "assert_select[#{self.iter_index}]:expects_any?" 52 | end 53 | 54 | provided_value = data[key] 55 | data_payload = { 56 | key: key, 57 | } 58 | 59 | if provided_value.nil? 60 | self.iter_fail!(kind: kind, data: data_payload, message: 'any items was found to be nil') 61 | return self 62 | end 63 | 64 | any_value = provided_value.any? do |item| 65 | result = block.call(item) 66 | end 67 | 68 | if any_value 69 | self.iter_pass!(kind: kind, data: data_payload, message: 'any item was found') 70 | else 71 | self.iter_fail!(kind: kind, data: data_payload, message: 'any item was not found') 72 | end 73 | return self 74 | end 75 | 76 | def expects_eq data, key, expected_value 77 | kind = "assert_select[#{self.iter_index}]:expects_eq" 78 | provided_value = data[key] 79 | data_payload = { 80 | key: key, 81 | provided_value: provided_value, 82 | expected_value: expected_value 83 | } 84 | if provided_value == expected_value 85 | self.iter_pass!(kind: kind, data: data_payload, message: 'value was equal to') 86 | else 87 | self.iter_fail!(kind: kind, data: data_payload, message: 'value was not equal to') 88 | end 89 | return self 90 | end 91 | 92 | def expects_true data, key 93 | value = data[key] 94 | kind = "assert_select[#{self.iter_index}]:expects_true" 95 | data_payload = { 96 | key: key, 97 | provided_value: value 98 | } 99 | if value == true 100 | self.iter_pass!(kind: kind, data: data_payload, message: 'value was found to be true') 101 | else 102 | self.iter_fail!(kind: kind, data: data_payload, message: 'value was not found to be true') 103 | end 104 | return self 105 | end 106 | 107 | def expects_false data, key 108 | value = data[key] 109 | kind = "assert_select[#{self.iter_index}]:expects_false" 110 | data_payload = { 111 | key: key, 112 | provided_value: value 113 | } 114 | if value == false 115 | self.iter_pass!(kind: kind, data: data_payload, message: 'value was found to be false') 116 | else 117 | self.iter_fail!(kind: kind, data: data_payload, message: 'value was not found to be false') 118 | end 119 | return self 120 | end 121 | 122 | def returns key 123 | kind = "assert_select:returns" 124 | data = @found_data 125 | if key == :all || key.nil? 126 | self.pass! kind: kind, message: 'return all data', data: { 127 | } 128 | return data 129 | end 130 | if !data.nil? && data.key?(key) 131 | self.pass!( 132 | kind: kind, 133 | message: 'return all data with provided key', 134 | data: { 135 | provided_key: key 136 | }) 137 | return data[key] 138 | else 139 | self.fail!( 140 | kind: kind, 141 | message: 'failed to return data with provided key since key does not exist or data is nil', 142 | data: { 143 | provided_key: key 144 | } 145 | ) 146 | end 147 | end 148 | 149 | def pass! kind:, message:, data: {} 150 | @report.pass!( 151 | describe_key: @describe_key, 152 | spec_key: @spec_key, 153 | kind: kind, 154 | message: message, 155 | data: data 156 | ) 157 | end 158 | 159 | def fail! kind:, message:, data: {} 160 | @report.fail!( 161 | describe_key: @describe_key, 162 | spec_key: @spec_key, 163 | kind: kind, 164 | message: message, 165 | data: data 166 | ) 167 | end 168 | 169 | def iter_pass! kind:, message:, data: {} 170 | @report.iter_pass!( 171 | describe_key: @describe_key, 172 | spec_key: @spec_key, 173 | kind: kind, 174 | message: message, 175 | data: data 176 | ) 177 | end 178 | 179 | def iter_fail! kind:, message:, data: {} 180 | @report.iter_fail!( 181 | describe_key: @describe_key, 182 | spec_key: @spec_key, 183 | kind: kind, 184 | message: message, 185 | data: data 186 | ) 187 | end 188 | 189 | def iter_index 190 | @report.iter_index @describe_key, @spec_key 191 | end 192 | end -------------------------------------------------------------------------------- /lib/cpbvt/tester/describe.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Tester::Describe 2 | attr_accessor :key, :specs 3 | 4 | def initialize key:, context: 5 | @key = key 6 | @specs = {} 7 | self.evaluate! context 8 | end 9 | 10 | # The magic that makes our DSL work. 11 | def evaluate! context 12 | @self_before_instance_eval = eval "self", context.binding 13 | instance_eval &context 14 | end 15 | 16 | def spec key, opts={}, &block 17 | key = key.to_sym 18 | # test is a revserved word which is why we say test_instances 19 | condition = opts[:condition] if opts.key?(:condition) 20 | spec_instance = Cpbvt::Tester::Spec.new( 21 | describe: self, 22 | key: key, 23 | condition: condition, 24 | context: block 25 | ) 26 | raise "spec key already in use: #{key}" if @specs.key?(key) 27 | @specs[key] = spec_instance 28 | end 29 | 30 | # We might want this later... 31 | # https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation 32 | #def method_missing(method, *args, **kwargs, &block) 33 | # @self_before_instance_eval.send method, *args, &block 34 | #end 35 | end 36 | -------------------------------------------------------------------------------- /lib/cpbvt/tester/report.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Tester::AssertFail < StandardError 2 | attr_reader :describe_key, 3 | :spec_key, 4 | :assert_data, 5 | :kind 6 | def initialize( 7 | msg="", 8 | describe_key, 9 | spec_key, 10 | kind, 11 | assert_data 12 | ) 13 | @describe_key = describe_key 14 | @spec_key = spec_key 15 | @assert_data = assert_data 16 | @kind = kind 17 | super(msg) 18 | end 19 | end 20 | 21 | class Cpbvt::Tester::Report 22 | def initialize 23 | @specs = {} 24 | end 25 | 26 | def add! describe_key, spec_key, condition 27 | @specs[describe_key] ||= {specs: {}} 28 | @specs[describe_key][:specs][spec_key] ||= {status: nil, asserts: [], condition: condition} 29 | end 30 | 31 | def pass! describe_key:, spec_key:, kind:, message:, data: {} 32 | @specs[describe_key][:specs][spec_key][:asserts].push({ 33 | kind: kind, 34 | status: 'pass', 35 | message: message, 36 | data: data 37 | }) 38 | end 39 | 40 | def fail! describe_key:, spec_key:, kind:, message:, data: {} 41 | @specs[describe_key][:specs][spec_key][:asserts].push({ 42 | kind: kind, 43 | status: 'fail', 44 | message: message, 45 | data: data 46 | }) 47 | raise Cpbvt::Tester::AssertFail.new( 48 | message, 49 | describe_key, 50 | spec_key, 51 | kind, 52 | data 53 | ) 54 | end 55 | 56 | def passed! describe_key:, spec_key: 57 | @specs[describe_key][:specs][spec_key][:status] = 'pass' 58 | end 59 | 60 | def failed! describe_key:, spec_key: 61 | @specs[describe_key][:specs][spec_key][:status] = 'fail' 62 | end 63 | 64 | # only for iter 65 | def iter_index describe_key, spec_key 66 | @specs[describe_key][:specs][spec_key][:asserts].last[:results].size-1 67 | end 68 | 69 | def iter_add!(describe_key:,spec_key:) 70 | @specs[describe_key][:specs][spec_key][:asserts].last[:results].push [] 71 | end 72 | 73 | def iter_start!(describe_key:, spec_key:, kind:) 74 | @specs[describe_key][:specs][spec_key][:asserts].push({ 75 | kind: kind, 76 | status: 'unknown', 77 | message: nil, 78 | results: [], 79 | data: nil 80 | }) 81 | end 82 | 83 | def iter_end!(describe_key:, spec_key:,status:,data:{}) 84 | spec = @specs[describe_key][:specs][spec_key][:asserts].last 85 | spec[:status] = status 86 | spec[:data] = data 87 | end 88 | 89 | def iter_pass! describe_key:, spec_key:, kind:, message:, data: {} 90 | spec = @specs[describe_key][:specs][spec_key][:asserts].last 91 | spec[:results].last.push({ 92 | kind: kind, 93 | status: 'pass', 94 | message: message, 95 | data: data 96 | }) 97 | end 98 | 99 | def iter_fail! describe_key:, spec_key:, kind:, message:, data: {} 100 | spec = @specs[describe_key][:specs][spec_key][:asserts].last 101 | spec[:results].last.push({ 102 | kind: kind, 103 | status: 'fail', 104 | message: message, 105 | data: data 106 | }) 107 | # we don't raise an error here 108 | end 109 | 110 | def iter_last describe_key, spec_key 111 | spec = @specs[describe_key][:specs][spec_key][:asserts].last[:results].last 112 | end 113 | 114 | def to_json 115 | total_spec_count = 0 116 | total_spec_pass_count = 0 117 | total_assert_count = 0 118 | total_assert_pass_count = 0 119 | status = 'pass' 120 | 121 | @specs.each do |group_name,group| 122 | group[:spec_pass_count] = 0 123 | total_spec_count += group[:specs].count 124 | group[:specs].each do |spec_name,spec| 125 | total_assert_count += spec[:asserts].count 126 | spec[:assert_pass_count] = 0 127 | if spec[:status] == 'pass' 128 | group[:spec_pass_count] += 1 129 | total_spec_pass_count += 1 130 | elsif spec[:status] == 'fail' && spec[:condition] != 'optional' 131 | status = 'fail' 132 | end 133 | spec[:asserts].each do |assert_obj| 134 | if assert_obj[:status] == 'pass' 135 | spec[:assert_pass_count] += 1 136 | total_assert_pass_count += 1 137 | end 138 | end # spec[:asserts] 139 | end # group[:specs] 140 | end 141 | 142 | report = { 143 | status: status, 144 | total_spec_count: total_spec_count, 145 | total_spec_pass_count: total_spec_pass_count, 146 | total_assert_count: total_assert_count, 147 | total_assert_pass_count: total_assert_pass_count, 148 | describes: @specs 149 | } 150 | report.to_json 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /lib/cpbvt/tester/runner.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | class Cpbvt::Tester::Runner 3 | @@describes = {} 4 | @@loaded = false 5 | 6 | def self.run!( 7 | validations_path:, 8 | load_order: [], 9 | manifest:, 10 | general_params:, 11 | specific_params:, 12 | dynamic_params: 13 | ) 14 | report = Cpbvt::Tester::Report.new 15 | # loads the validations files 16 | # and the registers the describes and their specs 17 | unless @loadded 18 | pattern_path_files = File.join(validations_path,"*.rb") 19 | path_files = Dir.glob(pattern_path_files) 20 | path_files.each do |path| 21 | require path 22 | end 23 | end 24 | 25 | # run the actual specs 26 | load_order ||= @@describe.keys 27 | load_order.each do |describe_key| 28 | describe_instance = @@describes[describe_key] 29 | if describe_instance.nil? 30 | raise "Failed to find the describe block based on the describe key: #{describe_key}: describe_keys: #{@@describes.keys.join(',')} load_order: #{load_order.join(',')}" 31 | end 32 | describe_instance.specs.each do |spec_key,spec_instance| 33 | begin 34 | spec_instance.evaluate! report, manifest, general_params, specific_params, dynamic_params 35 | spec_instance.passed! 36 | rescue Cpbvt::Tester::AssertFail => e 37 | spec_instance.failed! 38 | # just skip to the next one... 39 | next 40 | end 41 | end 42 | end 43 | return JSON.parse(report.to_json) 44 | end 45 | 46 | def self.describe key, &block 47 | describe = Cpbvt::Tester::Describe.new( 48 | key: key.to_sym, 49 | context: block 50 | ) 51 | raise "describe key already in use: #{key}" if @@describes.key?(key) 52 | @@describes[key] = describe 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/cpbvt/tester/spec.rb: -------------------------------------------------------------------------------- 1 | class Cpbvt::Tester::Spec 2 | attr_accessor :describe, 3 | :key, 4 | :pass_msg, 5 | :fail_msg, 6 | :status, 7 | :report, 8 | :condition, 9 | :manifest, 10 | :specific_params, 11 | :general_params, 12 | :dynamic_params 13 | 14 | ## condition can be: 15 | # optional - it doesn't matter if you fail this spec 16 | # critical (default) if you fail this spec then you fail 17 | 18 | # status can be: 19 | # ready - ready to run 20 | # pass - has ran and graded as passed 21 | # fail - has ran and graded as failed 22 | # skipped - never ran 23 | def initialize describe:, key:, condition:, context: 24 | @describe = describe 25 | @condition = condition 26 | @condition ||= 'normal' 27 | @key = key.to_sym 28 | @status = :ready # ready to run 29 | @context = context 30 | end 31 | 32 | # The magic that makes our DSL work. 33 | def evaluate!( 34 | report, 35 | manifest, 36 | general_params, 37 | specific_params, 38 | dynamic_params 39 | ) 40 | @report = report 41 | @report.add! self.describe.key, self.key, self.condition 42 | @manifest = manifest 43 | @general_params = general_params 44 | @specific_params = specific_params 45 | @dynamic_params = dynamic_params 46 | @self_before_instance_eval = eval "self", @context.binding 47 | #begin 48 | instance_eval &@context 49 | #rescue => e 50 | #binding.pry 51 | #end 52 | end 53 | 54 | def assert_cfn_resource stack_name, resource_type 55 | obj = Cpbvt::Tester::AssertCfnResource.new( 56 | describe_key: self.describe.key, 57 | spec_key: self.key, 58 | report: @report, 59 | manifest: @manifest, 60 | stack_name: stack_name, 61 | resource_type: resource_type 62 | ) 63 | return obj 64 | end 65 | 66 | def assert_start_with data, *args 67 | expected_value = args.pop 68 | key = nil 69 | key = args.first if args.length > 0 70 | kind = 'spec:start_with' 71 | 72 | provided_value = key ? data[key] : data 73 | 74 | payload_data = { 75 | provided_value: provided_value, 76 | expected_value: expected_value 77 | } 78 | if provided_value.start_with?(expected_value) 79 | self.pass!(kind: kind, message: 'value was equal to', data: payload_data) 80 | else 81 | self.fail!(kind: kind, message: 'value was not equal to', data: payload_data) 82 | end 83 | return self 84 | end 85 | 86 | def assert_end_with data, *args 87 | expected_value = args.pop 88 | key = nil 89 | key = args.first if args.length > 0 90 | kind = 'spec:end_with' 91 | 92 | provided_value = key ? data[key] : data 93 | 94 | payload_data = { 95 | provided_value: provided_value, 96 | expected_value: expected_value 97 | } 98 | if provided_value.end_with?(expected_value) 99 | self.pass!(kind: kind, message: 'value was equal to', data: payload_data) 100 | else 101 | self.fail!(kind: kind, message: 'value was not equal to', data: payload_data) 102 | end 103 | return self 104 | end 105 | 106 | def assert_eq data, *args 107 | expected_value = args.pop 108 | key = nil 109 | key = args.first if args.length > 0 110 | kind = 'spec:assert_eq' 111 | 112 | provided_value = key ? data[key] : data 113 | 114 | payload_data = { 115 | provided_value: provided_value, 116 | expected_value: expected_value 117 | } 118 | if provided_value == expected_value 119 | self.pass!(kind: kind, message: 'value was equal to', data: payload_data) 120 | else 121 | self.fail!(kind: kind, message: 'value was not equal to', data: payload_data) 122 | end 123 | return self 124 | end 125 | 126 | def assert_include? data, *args 127 | expected_value = args.pop 128 | key = nil 129 | key = args.first if args.length > 0 130 | 131 | kind = 'spec:assert_include?' 132 | provided_value = key ? data[key] : data 133 | 134 | payload_data = { 135 | provided_value: provided_value, 136 | expected_value: expected_value 137 | } 138 | if provided_value.include?(expected_value) 139 | self.pass!(kind: kind, message: 'value was included', data: payload_data) 140 | else 141 | self.fail!(kind: kind, message: 'value was not included', data: payload_data) 142 | end 143 | return self 144 | end 145 | 146 | def assert_not_nil data, label: nil 147 | kind = 148 | if label.nil? 149 | "assert_not_nil" 150 | else 151 | "assert_not_nil:#{label}" 152 | end 153 | unless data.nil? 154 | self.pass!(kind: kind, message: "Found that it not nil as expected", data: {}) 155 | else 156 | self.fail!(kind: kind, message: "It was found to be nil", data: {}) 157 | end 158 | end 159 | 160 | def assert_load manifest_payload_key, key=nil 161 | obj = Cpbvt::Tester::AssertLoad.new( 162 | describe_key: self.describe.key, 163 | spec_key: self.key, 164 | report: @report, 165 | manifest_payload_key: manifest_payload_key, 166 | manifest: @manifest, 167 | key: key 168 | ) 169 | return obj 170 | end 171 | 172 | def assert_json data, *keys 173 | obj = Cpbvt::Tester::AssertJson.new( 174 | describe_key: self.describe.key, 175 | spec_key: self.key, 176 | report: @report, 177 | data: data, 178 | keys: keys 179 | ) 180 | return obj 181 | end 182 | 183 | def assert_find data, &block 184 | obj = Cpbvt::Tester::AssertFind.new( 185 | describe_key: self.describe.key, 186 | spec_key: self.key, 187 | report: @report, 188 | data: data, 189 | context: block 190 | ) 191 | return obj 192 | end 193 | 194 | def assert_select data, &block 195 | obj = Cpbvt::Tester::AssertSelect.new( 196 | describe_key: self.describe.key, 197 | spec_key: self.key, 198 | report: @report, 199 | data: data, 200 | context: block 201 | ) 202 | return obj 203 | end 204 | 205 | def set_pass_message msg 206 | @pass_msg = msg 207 | end 208 | 209 | def set_fail_message msg 210 | @fail = msg 211 | end 212 | 213 | def set_state_value key, value 214 | @dynamic_params.send("#{key}=", value) 215 | end 216 | 217 | def pass! kind:, message:, data: {} 218 | @report.pass!( 219 | describe_key: self.describe.key, 220 | spec_key: self.key, 221 | kind: kind, 222 | message: message, 223 | data: data 224 | ) 225 | end 226 | 227 | def fail! kind:, message:, data: {} 228 | @report.fail!( 229 | describe_key: self.describe.key, 230 | spec_key: self.key, 231 | kind: kind, 232 | message: message, 233 | data: data 234 | ) 235 | end 236 | 237 | def passed! 238 | @report.passed!( 239 | describe_key: self.describe.key, 240 | spec_key: self.key) 241 | end 242 | 243 | def failed! 244 | @report.failed!( 245 | describe_key: self.describe.key, 246 | spec_key: self.key) 247 | end 248 | end 249 | -------------------------------------------------------------------------------- /lib/cpbvt/uploader.rb: -------------------------------------------------------------------------------- 1 | require 'aws-sdk-s3' 2 | 3 | # This class is response for uploading json files to S3 4 | # The files contain about from API calls from CLI tools 5 | # We are storing them to hold on for auditing. 6 | class Cpbvt::Uploader 7 | # file_path - the path to the json file to be uploaded 8 | # object_key - the s3 object key that the file will use in the bucket 9 | def self.run(file_path:, 10 | object_key:, 11 | aws_region:, 12 | aws_access_key_id:, 13 | aws_secret_access_key:, 14 | payloads_bucket:, 15 | public_read: false) 16 | Aws.config.update({ 17 | credentials: Aws::Credentials.new( 18 | aws_access_key_id, 19 | aws_secret_access_key 20 | ) 21 | }) 22 | 23 | # Create an S3 client 24 | s3_client = Aws::S3::Client.new 25 | 26 | # Read the JSON file content 27 | file_content = File.read(file_path) 28 | 29 | begin 30 | # Upload the JSON file to S3 31 | attrs = { 32 | bucket: payloads_bucket, 33 | key: object_key, 34 | body: file_content 35 | } 36 | # when working with azure arm templates we need to make them public in S3 37 | # the payload bucket also requires a CORS policy to allow portal.azure.com to GET 38 | # nothing sensitive is contained inside the ligthhouse template and it is removed after validation is complete 39 | if public_read == true 40 | attrs[:acl] = 'public-read' 41 | end 42 | s3_client.put_object attrs 43 | puts 'File uploaded successfully.' 44 | rescue Aws::S3::Errors::ServiceError => e 45 | puts "AWS S3 service error: #{e.message}" 46 | rescue Errno::ENOENT => e 47 | puts "File not found error: #{e.message}" 48 | rescue StandardError => e 49 | puts "Error uploading file: #{e.message}" 50 | end 51 | end # self.run 52 | 53 | # return a presigned url for uploaded files - required when a user must download templates 54 | def self.presigned_url key:, 55 | payloads_bucket: 56 | Aws::S3::Presigner.new.presigned_url(:get_object, bucket: payloads_bucket, key: key) 57 | end 58 | 59 | # create the key to where the json file will be uploaded into the bucket 60 | def self.object_key user_uuid:, 61 | project_scope:, 62 | run_uuid:, 63 | region:, 64 | filename: 65 | value = File.join( 66 | project_scope, 67 | "user-#{user_uuid}", 68 | "run-#{run_uuid}", 69 | region, 70 | filename 71 | ) 72 | 73 | # print the desination of the outputed json 74 | #puts "[Output Key] #{value}" 75 | 76 | return value 77 | end 78 | end # class 79 | -------------------------------------------------------------------------------- /lib/cpbvt/version.rb: -------------------------------------------------------------------------------- 1 | module Cpbvt 2 | VERSION = "1.4.5" 3 | end 4 | -------------------------------------------------------------------------------- /playground/cli-azure.rb: -------------------------------------------------------------------------------- 1 | # This is a wip script to explore how to work with the Azure CLI 2 | require 'fileutils' 3 | require 'ostruct' 4 | require 'time' 5 | require 'open3' 6 | 7 | # Permission Setup 8 | 9 | # Deploy the Azure Lighthouse ARM template to grant the validator access to resources inside the customer account 10 | # Access is delegated to a security group in the source tenant with the Contributor role on a specified resource group in the customer account 11 | # https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles 12 | # The Azure service principal that is used to run the validator will need to be added to the lighthouse security group in the source tenant 13 | 14 | # ARM Files 15 | # /workspace/cloud-project-bootcamp-validation-tool/lib/cpbvt/payloads/azure/lighthouse-params.json 16 | # /workspace/cloud-project-bootcamp-validation-tool/lib/cpbvt/payloads/azure/lighthouse-template.json 17 | 18 | # Deploy via cli 19 | 20 | # az deployment sub create --name \ 21 | # --location \ 22 | # --template-uri \ 23 | # --parameters \ 24 | # --verbose 25 | # 26 | 27 | # Deploy via PowerShell 28 | 29 | # New-AzSubscriptionDeployment -Name ` 30 | # -Location ` 31 | # -TemplateUri ` 32 | # -TemplateParameterUri ` 33 | # -Verbose 34 | 35 | # One-click Deployment Button (Markdown) 36 | # [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/%26api-version%3D6.0) 37 | 38 | # These env vars are obtained by registering an application 39 | # with Azure Active Directory (Azure AD) know known as Microsoft Entra ID 40 | 41 | # Entra ID > Enterprise Applications > All Applications 42 | # https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps 43 | 44 | # We need to authenicate by creating a service principle 45 | # https://learn.microsoft.com/en-us/cli/azure/azure-cli-sp-tutorial-1?tabs=bash 46 | 47 | # After creating a service principal in the Azure Active Directory you need to give this new user some roles within a subscription: 48 | # 49 | # go to your subscription 50 | # go to Access Control (IAM) 51 | # Add a roles assignment (for instance make your service principal contributor) 52 | 53 | # RePreq 54 | # - In a subscription, you must have User Access Administrator or Role Based Access Control Administrator permissions, or higher, to create a service principal. 55 | # - Create it via Azure Cloud Shell 56 | 57 | # This command will create the env vars required 58 | # az ad sp create-for-rbac 59 | 60 | # az storage account list 61 | 62 | env_vars = { 63 | "AZURE_CLIENT_ID" => ENV["AZURE_CLIENT_ID"], 64 | "AZURE_TENANT_ID" => ENV["AZURE_TENANT_ID"], 65 | "AZURE_CLIENT_SECRET" => ENV["AZURE_CLIENT_SECRET"] 66 | } 67 | 68 | # user supplied parameters 69 | account_name = 'examprostorage' 70 | container_name = 'examprocontainer' 71 | blob_name = 'examproblob.txt' 72 | user_subscription_id = 'cf5d6d10-f3fd-442f-ae59-80dae20c8fa4' 73 | 74 | login = < --member serviceAccount:@.iam.gserviceaccount.com --role=roles/viewer 28 | 29 | list_comamnd = <