├── AWS ├── S3 │ ├── S3-profile.yaml │ ├── resourceTemplates │ │ ├── values.yaml │ │ ├── values.secret.example.yaml │ │ ├── templates │ │ │ ├── anotherBucket.yaml │ │ │ ├── BasicBucket.yaml │ │ │ ├── VersioningBucket.yaml │ │ │ ├── BlockPublicBucket.yaml │ │ │ ├── NotificationBucket.yaml │ │ │ ├── VersioningLifecycleBucket.yaml │ │ │ ├── LoggingBucket.yaml │ │ │ ├── BasicBucketPolicy.yaml │ │ │ ├── EncryptedBucket.yaml │ │ │ ├── UnauthorisedBucket.yaml │ │ │ ├── UnauthorisedBucketPolicy.yaml │ │ │ ├── AuthorisedBucket │ │ │ │ ├── AuthorisedBucketPolicy.yaml │ │ │ │ ├── AuthorisedBucketTopic.yaml │ │ │ │ ├── AuthorisedBucket.yaml │ │ │ │ └── replicationRole.yaml │ │ │ ├── NotificationTopic.yaml │ │ │ └── ReplicationBucket.yaml │ │ ├── .helmignore │ │ └── Chart.yaml │ ├── variables.yaml │ ├── Policies │ │ ├── s3-bucket-versioning-enabled.yaml │ │ ├── s3-bucket-replication-enabled.yaml │ │ ├── s3-version-lifecycle-policy-check.yaml │ │ ├── s3-default-encryption-kms.yaml │ │ ├── s3-bucket-server-side-encryption-enabled.yaml │ │ ├── s3-bucket-logging-enabled.yaml │ │ ├── s3-event-notifications-enabled.yaml │ │ ├── s3-bucket-ssl-requests-only.yaml │ │ └── s3-bucket-level-public-access-prohibited.yaml │ ├── README.md │ ├── bats-tests │ │ ├── test-require-versioning.bats │ │ ├── test-logging.bats │ │ ├── test-version-lifecycle.bats │ │ ├── test-replication.bats │ │ ├── test-notifcations-enabled.bats │ │ ├── test-require-SSL.bats │ │ ├── test-require-kms.bats │ │ ├── test-SSE.bats │ │ └── test-public-access.bats │ ├── NIST_SP-800-53_rev5_S3-baseline_profile.yaml │ └── kyverno-test.yaml ├── RDS │ ├── resourceTemplates │ │ ├── values.yaml │ │ ├── values.secret.example.yaml │ │ ├── templates │ │ │ ├── AuthorisedRDSRolePolicyAttachment.yaml │ │ │ ├── BasicRDSInstance.yaml │ │ │ ├── RDSInstance.yaml │ │ │ ├── AuthorisedRDSRole.yaml │ │ │ └── AuthorisedRDSInstance.yaml │ │ ├── .helmignore │ │ └── Chart.yaml │ ├── Policies │ │ ├── rds-multi-az-support.yaml │ │ ├── rds-storage-encrypted.yaml │ │ ├── rds-instance-public-access-check.yaml │ │ ├── rds-logging-enabled.yaml │ │ ├── rds-enhanced-monitoring-enabled.yaml │ │ ├── rds-instance-deletion-protection-enabled.yaml │ │ └── rds-instance-default-admin-check.yaml │ ├── README.md │ ├── bats-tests │ │ ├── test-multi-az-support.bats │ │ ├── test-default-admin-check.bats │ │ ├── test-logging-enabled.bats │ │ ├── test-storage-encrypted.bats │ │ ├── test-public-access-check.bats │ │ ├── test-enhanced-monitoring-enabled.bats │ │ └── test-deletion-protection-enabled.bats │ ├── NIST_SP-800-53_rev5_RDS-baseline_profile.yaml │ └── kyverno-test.yaml ├── Bootstrap │ ├── cluster │ │ ├── data.tf │ │ ├── variables.tf │ │ ├── outputs.tf │ │ ├── provider.tf │ │ └── main.tf │ ├── demoResources │ │ ├── variables.tf │ │ ├── outputs.tf │ │ ├── role.yaml │ │ ├── provider.tf │ │ └── main.tf │ ├── crossplane │ │ ├── main.tf │ │ └── provider.tf │ ├── provider │ │ ├── variables.tf │ │ ├── provider.tf │ │ └── main.tf │ ├── init.sh │ ├── readme.md │ ├── down.sh │ └── up.sh └── setup_suite.bash ├── Tools ├── loadcsv │ ├── .gitignore │ ├── requirements.txt │ ├── Readme.md │ └── loadcsv.py └── scripts │ ├── lintHelm.sh │ ├── rename.sh │ ├── generateResources.sh │ └── generatePolicies.sh ├── .github ├── CODEOWNERS └── workflows │ ├── lint.yaml │ └── kyverno-test.yaml ├── docs └── assets │ └── collie_logo.png ├── MAINTAINERS ├── bats └── test_helper │ ├── setup.bash │ └── helpers.sh ├── .tflint.hcl ├── .gitignore ├── SECURITY.md ├── .gitmodules ├── .yamllint.yaml ├── Makefile ├── README.md ├── LICENSE └── v4_uuids.txt /AWS/S3/S3-profile.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Tools/loadcsv/.gitignore: -------------------------------------------------------------------------------- 1 | testdata 2 | -------------------------------------------------------------------------------- /Tools/loadcsv/requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==6.0 2 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/values.yaml: -------------------------------------------------------------------------------- 1 | region: eu-west-2 2 | -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/values.yaml: -------------------------------------------------------------------------------- 1 | region: eu-west-2 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global Repository Owners 2 | * @rowan-baker @hennersz -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/values.secret.example.yaml: -------------------------------------------------------------------------------- 1 | accountID: "112233445566" 2 | -------------------------------------------------------------------------------- /AWS/Bootstrap/cluster/data.tf: -------------------------------------------------------------------------------- 1 | data "http" "home_ip" { 2 | url = "http://ipv4.icanhazip.com" 3 | } -------------------------------------------------------------------------------- /docs/assets/collie_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/controlplaneio/collie/HEAD/docs/assets/collie_logo.png -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Rowan Baker, ControlPlane 2 | Henry Mortimer, ControlPlane -------------------------------------------------------------------------------- /bats/test_helper/setup.bash: -------------------------------------------------------------------------------- 1 | _common_setup() { 2 | load "$ROOT/bats/test_helper/bats-support/load" 3 | load "$ROOT/bats/test_helper/bats-assert/load" 4 | load "$ROOT/bats/test_helper/helpers.sh" 5 | } -------------------------------------------------------------------------------- /AWS/S3/variables.yaml: -------------------------------------------------------------------------------- 1 | policies: 2 | - name: s3-bucket-ssl-requests-only 3 | rules: 4 | - name: s3-bucket-ssl-requests-only-check-policy-exists 5 | values: 6 | bucketnameref: [ "basic-bucket", "different-bucket" ] 7 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/values.secret.example.yaml: -------------------------------------------------------------------------------- 1 | replication: 2 | keyARN: arn:aws:kms:eu-west-2:112233445566:key/12345 3 | bucketARN: arn:aws:s3:::test-bucket 4 | accountID: "112233445566" 5 | loggingBucket: test-bucket 6 | suffix: a1b2c3 7 | -------------------------------------------------------------------------------- /.tflint.hcl: -------------------------------------------------------------------------------- 1 | plugin "terraform" { 2 | enabled = true 3 | preset = "recommended" 4 | } 5 | 6 | plugin "aws" { 7 | deep_check = true 8 | enabled = true 9 | version = "0.21.2" 10 | source = "github.com/terraform-linters/tflint-ruleset-aws" 11 | } -------------------------------------------------------------------------------- /AWS/Bootstrap/cluster/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | type = string 3 | default = "eu-west-2" 4 | } 5 | 6 | variable "aws_profile" { 7 | type = string 8 | default = "default" 9 | } 10 | 11 | variable "project_name" { 12 | type = string 13 | } -------------------------------------------------------------------------------- /AWS/Bootstrap/demoResources/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | type = string 3 | default = "eu-west-2" 4 | } 5 | 6 | variable "aws_profile" { 7 | type = string 8 | default = "default" 9 | } 10 | 11 | variable "suffix" { 12 | type = string 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore terraform files 2 | *.tfstate 3 | .terraform.lock.hcl 4 | .terraform 5 | terraform.tfstate.backup 6 | *.tfvars 7 | 8 | # cleanup lula work 9 | /Tools/out/* 10 | /Tools/Policies/ 11 | /AWS/S3/out/ 12 | 13 | Resources 14 | values.secret.yaml 15 | 16 | .lulapolicies 17 | -------------------------------------------------------------------------------- /bats/test_helper/helpers.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | patch_and_apply_yaml() { 4 | yq ". * ( \"$1\" | from_yaml )" $2 | kubectl apply -f - 5 | } 6 | 7 | apply_policy_with_label_selector() { 8 | yq " .spec.rules[].match.resources.selector.matchLabels.test |= \"$1\"" "$2" | kubectl apply -f - 9 | } -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/anotherBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: another-bucket 5 | spec: 6 | forProvider: 7 | acl: private 8 | locationConstraint: {{ .Values.region }} 9 | objectOwnership: BucketOwnerPreferred 10 | -------------------------------------------------------------------------------- /AWS/Bootstrap/crossplane/main.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "helm_release" "crossplane" { 3 | name = "crossplane" 4 | 5 | repository = "https://charts.crossplane.io/stable" 6 | chart = "crossplane" 7 | version = "1.12.0" 8 | 9 | namespace = "crossplane-system" 10 | create_namespace = true 11 | } 12 | -------------------------------------------------------------------------------- /AWS/Bootstrap/provider/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | type = string 3 | default = "eu-west-2" 4 | } 5 | 6 | variable "aws_profile" { 7 | type = string 8 | default = "default" 9 | } 10 | 11 | variable "oidc_provider" { 12 | type = string 13 | } 14 | 15 | variable "suffix" { 16 | type = string 17 | } -------------------------------------------------------------------------------- /AWS/Bootstrap/crossplane/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | helm = { 4 | source = "hashicorp/helm" 5 | version = "~> 2.6.0" 6 | } 7 | } 8 | 9 | required_version = ">= 1.2.3" 10 | } 11 | 12 | provider "helm" { 13 | kubernetes { 14 | config_path = "~/.kube/config" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AWS/Bootstrap/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -evo pipefail 4 | 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | 7 | cd "$SCRIPT_DIR/cluster" 8 | terraform init 9 | 10 | cd "$SCRIPT_DIR/crossplane" 11 | terraform init 12 | 13 | cd "$SCRIPT_DIR/provider" 14 | terraform init 15 | 16 | cd "$SCRIPT_DIR/demoResources" 17 | terraform init 18 | -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/templates/AuthorisedRDSRolePolicyAttachment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: iam.aws.crossplane.io/v1beta1 2 | kind: RolePolicyAttachment 3 | metadata: 4 | name: authorised-rds-role-policy-attachment 5 | spec: 6 | forProvider: 7 | policyArn: arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole 8 | roleNameRef: 9 | name: authorised-rds-role 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Please report (suspected) security vulnerabilities to security@control-plane.io. Optional encryption keys are available at https://keybase.io/sublimino/pgp_keys.asc 4 | 5 | You will receive a response from us within 48 hours. If the issue is confirmed, we will release a patch as soon as possible depending on complexity, but historically within a few days. 6 | -------------------------------------------------------------------------------- /AWS/Bootstrap/cluster/outputs.tf: -------------------------------------------------------------------------------- 1 | output "cluster_id" { 2 | value = module.eks.cluster_id 3 | } 4 | 5 | output "oidc_provider" { 6 | value = module.eks.oidc_provider 7 | } 8 | 9 | output "aws_profile" { 10 | value = var.aws_profile 11 | } 12 | 13 | output "aws_region" { 14 | value = var.aws_region 15 | } 16 | 17 | output "suffix" { 18 | value = lower(random_string.suffix.result) 19 | } -------------------------------------------------------------------------------- /Tools/scripts/lintHelm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) 6 | ROOT_DIR=$(cd "$SCRIPT_DIR"/../.. && pwd) 7 | 8 | function helm_lint() { 9 | helm lint "$1" -f "$1/values.secret.example.yaml" 10 | } 11 | 12 | for dir in "$ROOT_DIR"/**/**/resourceTemplates; do 13 | helm_lint "$dir" 14 | done 15 | -------------------------------------------------------------------------------- /AWS/Bootstrap/demoResources/outputs.tf: -------------------------------------------------------------------------------- 1 | output "logging_bucket_name" { 2 | value = aws_s3_bucket.logging_bucket.bucket 3 | } 4 | 5 | output "account_id" { 6 | value = data.aws_caller_identity.current.account_id 7 | } 8 | 9 | output "replication_bucket_arn" { 10 | value = aws_s3_bucket.replication_bucket.arn 11 | } 12 | 13 | output "replication_encryption_key_arn" { 14 | value = aws_kms_key.replication_encryption_key.arn 15 | } -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/templates/BasicRDSInstance.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: database.aws.crossplane.io/v1beta1 2 | kind: RDSInstance 3 | metadata: 4 | name: basic-rdsinstance 5 | spec: 6 | forProvider: 7 | masterUsername: postgres 8 | allocatedStorage: 20 9 | dbInstanceClass: db.t2.small 10 | engine: postgres 11 | engineVersion: "12" 12 | region: {{ .Values.region }} 13 | skipFinalSnapshotBeforeDeletion: true 14 | -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Tools/lula"] 2 | path = Tools/lula 3 | url = git@github.com:defenseunicorns/lula.git 4 | [submodule "bats/test_helper/bats-support"] 5 | path = bats/test_helper/bats-support 6 | url = https://github.com/bats-core/bats-support.git 7 | [submodule "bats/test_helper/bats-assert"] 8 | path = bats/test_helper/bats-assert 9 | url = https://github.com/bats-core/bats-assert.git 10 | [submodule "bats/bats-core"] 11 | path = bats/bats-core 12 | url = https://github.com/bats-core/bats-core.git 13 | -------------------------------------------------------------------------------- /AWS/Bootstrap/cluster/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 4.20.1" 6 | } 7 | 8 | http = { 9 | source = "hashicorp/http" 10 | version = "3.2.1" 11 | } 12 | 13 | random = { 14 | source = "hashicorp/random" 15 | version = "3.4.3" 16 | } 17 | } 18 | 19 | required_version = ">= 1.2.3" 20 | } 21 | 22 | provider "aws" { 23 | profile = var.aws_profile 24 | region = var.aws_region 25 | } 26 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/BasicBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: basic-bucket 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: dci-basic-bucket-{{ .Values.suffix }} 9 | spec: 10 | forProvider: 11 | acl: public-read 12 | locationConstraint: {{ .Values.region }} 13 | objectOwnership: BucketOwnerPreferred 14 | -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/templates/RDSInstance.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: database.aws.crossplane.io/v1beta1 2 | kind: RDSInstance 3 | metadata: 4 | name: example-rdsinstance 5 | spec: 6 | forProvider: 7 | allocatedStorage: 20 8 | dbInstanceClass: db.t2.small 9 | engine: postgres 10 | engineVersion: "12" 11 | masterUsername: masteruser 12 | publiclyAccessible: true 13 | region: {{ .Values.region }} 14 | skipFinalSnapshotBeforeDeletion: true 15 | writeConnectionSecretToRef: 16 | namespace: crossplane-system 17 | name: rds-pw-out-example 18 | -------------------------------------------------------------------------------- /Tools/scripts/rename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | for FILE in "${1%/}"/* ; do 5 | filename=$(basename -- "$FILE") 6 | uuid="${filename%.*}" 7 | description=$(yq -r " .\"component-definition\".components[].\"control-implementations\"[].\"implemented-requirements\"[] | select(.uuid == \"$uuid\") | .description" "$2" | head -n1) 8 | arrIN=(${description//" "/ }) 9 | yq ".spec.validationFailureAction |= \"enforce\"" "$FILE" > Policies/"file2".yaml 10 | yq ".metadata.name |= \"${arrIN[0]}\"" "Policies/file2.yaml" > Policies/"${arrIN[0]}".yaml 11 | rm -f Policies/file2.yaml 12 | done 13 | -------------------------------------------------------------------------------- /AWS/Bootstrap/demoResources/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: iam.aws.crossplane.io/v1beta1 2 | kind: Role 3 | metadata: 4 | name: dci-bucket-replication-role 5 | spec: 6 | forProvider: 7 | assumeRolePolicyDocument: | 8 | { 9 | "Version": "2012-10-17", 10 | "Statement": [ 11 | { 12 | "Effect": "Allow", 13 | "Principal": { 14 | "Service": [ 15 | "s3.amazonaws.com", 16 | ] 17 | }, 18 | "Action": [ 19 | "sts:AssumeRole" 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/templates/AuthorisedRDSRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: iam.aws.crossplane.io/v1beta1 2 | kind: Role 3 | metadata: 4 | name: authorised-rds-role 5 | spec: 6 | forProvider: 7 | assumeRolePolicyDocument: | 8 | { 9 | "Version": "2012-10-17", 10 | "Statement": [ 11 | { 12 | "Sid": "", 13 | "Effect": "Allow", 14 | "Principal": { 15 | "Service": "monitoring.rds.amazonaws.com" 16 | }, 17 | "Action": "sts:AssumeRole" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/VersioningBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: versioning-bucket 5 | annotations: 6 | crossplane.io/external-name: versioning-bucket-{{ .Values.suffix }} 7 | spec: 8 | forProvider: 9 | acl: private 10 | locationConstraint: {{ .Values.region }} 11 | objectOwnership: BucketOwnerPreferred 12 | publicAccessBlockConfiguration: 13 | blockPublicAcls: true 14 | blockPublicPolicy: true 15 | ignorePublicAcls: true 16 | restrictPublicBuckets: true 17 | versioningConfiguration: 18 | status: Enabled 19 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | ignore: | 4 | /Tools/lula/ 5 | Resources 6 | .terraform 7 | bats/bats-core/ 8 | bats/test_helper/bats-assert/ 9 | bats/test_helper/bats-support/ 10 | 11 | rules: 12 | comments: 13 | min-spaces-from-content: 1 14 | line-length: 15 | max: 250 16 | allow-non-breakable-inline-mappings: true 17 | truthy: 18 | check-keys: false 19 | document-start: disable 20 | indentation: 21 | spaces: 2 22 | check-multi-line-strings: false 23 | brackets: 24 | max-spaces-inside: 1 25 | max-spaces-inside-empty: 0 26 | braces: 27 | max-spaces-inside: 1 28 | max-spaces-inside-empty: 0 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | unit-test: generate-resources generate-policies 2 | kyverno test ./ 3 | 4 | generate-resources: 5 | ./Tools/scripts/generateResources.sh 6 | 7 | generate-policies: build-lula 8 | ./Tools/scripts/generatePolicies.sh 9 | 10 | build-lula: 11 | cd Tools/lula && make build 12 | 13 | lint: yaml-lint helm-lint terraform-lint 14 | 15 | yaml-lint: 16 | yamllint . 17 | 18 | helm-lint: 19 | Tools/scripts/lintHelm.sh 20 | 21 | terraform-lint: 22 | tflint --recursive 23 | terraform fmt -recursive -check -diff 24 | 25 | bats-test: 26 | ./bats/bats-core/bin/bats --jobs 4 -r ./AWS 27 | 28 | bats-quick-test: 29 | ./bats/bats-core/bin/bats -r --filter-tags "!speed:slow" --jobs 4 ./AWS -------------------------------------------------------------------------------- /AWS/Bootstrap/demoResources/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 4.20.1" 6 | } 7 | 8 | kubectl = { 9 | source = "gavinbunney/kubectl" 10 | version = "~> 1.14.0" 11 | } 12 | 13 | kubernetes = { 14 | source = "hashicorp/kubernetes" 15 | version = "~> 2.16.1" 16 | } 17 | } 18 | 19 | required_version = ">= 1.2.3" 20 | } 21 | 22 | provider "aws" { 23 | profile = var.aws_profile 24 | region = var.aws_region 25 | } 26 | 27 | provider "kubectl" { 28 | config_path = "~/.kube/config" 29 | } 30 | 31 | provider "kubernetes" { 32 | config_path = "~/.kube/config" 33 | } -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/BlockPublicBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: block-public-access 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: block-public-bucket-{{ .Values.suffix }} 9 | spec: 10 | forProvider: 11 | acl: private 12 | locationConstraint: {{ .Values.region }} 13 | objectOwnership: BucketOwnerPreferred 14 | publicAccessBlockConfiguration: 15 | blockPublicAcls: true 16 | blockPublicPolicy: true 17 | ignorePublicAcls: true 18 | restrictPublicBuckets: true 19 | -------------------------------------------------------------------------------- /AWS/Bootstrap/readme.md: -------------------------------------------------------------------------------- 1 | # AWS cluster bootstrapping 2 | 3 | To aid testing and development of new policies for AWS there is some terraform to provision an EKS cluster for you and install crossplane and kyverno. Provisioning happens in 2 stages because the kubernetes provider can't handle dependencies properly itself. There are helper script to run the terraform commands in the right order and pass variables around. 4 | 5 | ## Quickstart 6 | 7 | To create a cluster first run `init.sh` to pull the terraform dependencies for both stages. You also need to define the variables (via tfvars) `project_name`. Optionally you can also define `aws_region` and `aws_profile` 8 | 9 | Then to bring up the cluster run `up.sh`. Once this is complete you should have a cluster ready to go. -------------------------------------------------------------------------------- /AWS/setup_suite.bash: -------------------------------------------------------------------------------- 1 | setup_suite() { 2 | ROOT="$( cd "$( dirname "$BATS_TEST_FILENAME" )/.." >/dev/null 2>&1 && pwd )" 3 | export ROOT 4 | 5 | if [ "$SKIP_TERRAFORM" != "true" ] 6 | then 7 | cd "$ROOT/AWS/Bootstrap" || exit 1 8 | ./up.sh -d >&3 9 | fi 10 | 11 | cd "$ROOT/AWS/Bootstrap/cluster" || exit 1 12 | export SUFFIX="$(terraform output -raw suffix)" 13 | 14 | cd "$ROOT" || exit 1 15 | make generate-resources >&3 16 | make generate-policies >&3 17 | } 18 | 19 | teardown_suite() { 20 | if [ "$PRESERVE_CLUSTER" == "true" ] 21 | then 22 | return 0 23 | fi 24 | 25 | echo "Tearding down cluster, set \$PRESERVE_CLUSTER to 'true' to skip this" >&3 26 | cd "$ROOT/AWS/Bootstrap" || exit 1 27 | ./down.sh -d >&3 28 | } -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/NotificationBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: notification-bucket 5 | annotations: 6 | crossplane.io/external-name: notification-bucket-{{ .Values.suffix }} 7 | spec: 8 | forProvider: 9 | notificationConfiguration: 10 | topicConfigurations: 11 | - ID: notification-one 12 | events: [s3:ObjectCreated:*] 13 | topicRef: 14 | name: notification-bucket-topic 15 | acl: private 16 | locationConstraint: {{ .Values.region }} 17 | objectOwnership: BucketOwnerPreferred 18 | publicAccessBlockConfiguration: 19 | blockPublicAcls: true 20 | blockPublicPolicy: true 21 | ignorePublicAcls: true 22 | restrictPublicBuckets: true 23 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/VersioningLifecycleBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: versioning-lifecycle-bucket 5 | annotations: 6 | crossplane.io/external-name: versioning-lifecycle-bucket-{{ .Values.suffix }} 7 | spec: 8 | forProvider: 9 | acl: private 10 | locationConstraint: {{ .Values.region }} 11 | objectOwnership: BucketOwnerPreferred 12 | publicAccessBlockConfiguration: 13 | blockPublicAcls: true 14 | blockPublicPolicy: true 15 | ignorePublicAcls: true 16 | restrictPublicBuckets: true 17 | versioningConfiguration: 18 | status: Enabled 19 | lifecycleConfiguration: 20 | rules: 21 | - abortIncompleteMultipartUpload: 22 | daysAfterInitiation: 42 23 | status: Enabled 24 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/LoggingBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: logging-bucket 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: logging-bucket-{{ .Values.suffix }} 9 | spec: 10 | forProvider: 11 | loggingConfiguration: 12 | targetBucket: {{ .Values.loggingBucket }} 13 | targetPrefix: logs 14 | acl: private 15 | locationConstraint: {{ .Values.region }} 16 | objectOwnership: BucketOwnerPreferred 17 | publicAccessBlockConfiguration: 18 | blockPublicAcls: true 19 | blockPublicPolicy: true 20 | ignorePublicAcls: true 21 | restrictPublicBuckets: true 22 | -------------------------------------------------------------------------------- /AWS/RDS/Policies/rds-multi-az-support.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: rds-multi-az-support 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - RDSInstance 17 | mutate: {} 18 | name: rds-multi-az-support 19 | validate: 20 | message: rds-multi-az-support must be enabled. Set spec.forProvider.multiAZ. 21 | pattern: 22 | spec: 23 | forProvider: 24 | multiAZ: true 25 | validationFailureAction: enforce 26 | status: 27 | autogen: {} 28 | ready: false 29 | rulecount: 30 | generate: 0 31 | mutate: 0 32 | validate: 0 33 | verifyimages: 0 34 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/BasicBucketPolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1alpha3 2 | kind: BucketPolicy 3 | metadata: 4 | name: basic-bucketpolicy-deny-http 5 | spec: 6 | forProvider: 7 | region: {{ .Values.region }} 8 | bucketNameRef: 9 | name: basic-bucket 10 | policy: 11 | version: '2012-10-17' 12 | statements: 13 | - sid: "denyHTTP" 14 | action: 15 | - S3:GetObject 16 | principal: 17 | awsPrincipals: 18 | - iamRoleArn: "*" 19 | - iamUserArn: "*" 20 | effect: Deny 21 | condition: 22 | - operatorKey: Bool 23 | conditions: 24 | - key: "aws:SecureTransport" 25 | booleanValue: false 26 | resource: 27 | - "arn:aws:s3:::dci-basic-bucket/*" 28 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/EncryptedBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: encrypted-bucket 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: encrypted-bucket-{{ .Values.suffix }} 9 | spec: 10 | forProvider: 11 | acl: private 12 | locationConstraint: {{ .Values.region }} 13 | objectOwnership: BucketOwnerPreferred 14 | publicAccessBlockConfiguration: 15 | blockPublicAcls: true 16 | blockPublicPolicy: true 17 | ignorePublicAcls: true 18 | restrictPublicBuckets: true 19 | serverSideEncryptionConfiguration: 20 | rules: 21 | - applyServerSideEncryptionByDefault: 22 | sseAlgorithm: aws:kms 23 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/UnauthorisedBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: dci-unauthorised-bucket 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: dci-unauthorised-bucket 9 | spec: 10 | forProvider: 11 | acl: private 12 | locationConstraint: {{ .Values.region }} 13 | objectOwnership: BucketOwnerPreferred 14 | publicAccessBlockConfiguration: 15 | blockPublicAcls: false 16 | blockPublicPolicy: false 17 | ignorePublicAcls: false 18 | restrictPublicBuckets: false 19 | serverSideEncryptionConfiguration: 20 | rules: 21 | - applyServerSideEncryptionByDefault: 22 | sseAlgorithm: "aws" 23 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/UnauthorisedBucketPolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1alpha3 2 | kind: BucketPolicy 3 | metadata: 4 | name: unauthorised-bucketpolicy 5 | spec: 6 | forProvider: 7 | region: {{ .Values.region }} 8 | bucketNameRef: 9 | name: basic-bucket 10 | policy: 11 | version: '2012-10-17' 12 | statements: 13 | - sid: "denyHTTP" 14 | action: 15 | - S3:GetObject 16 | principal: 17 | awsPrincipals: 18 | - iamRoleArn: "*" 19 | - iamUserArn: "*" 20 | effect: Allow 21 | condition: 22 | - operatorKey: Bool 23 | conditions: 24 | - key: "aws:SecureTransport" 25 | booleanValue: false 26 | resource: 27 | - "arn:aws:s3:::collie-basic-bucket/*" 28 | -------------------------------------------------------------------------------- /AWS/RDS/Policies/rds-storage-encrypted.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: rds-storage-encrypted 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - RDSInstance 17 | mutate: {} 18 | name: rds-storage-encrypted 19 | validate: 20 | message: rds-storage-encrypted must be enabled. Set spec.forProvider.multiAZ.storageEncrypted 21 | pattern: 22 | spec: 23 | forProvider: 24 | storageEncrypted: true 25 | validationFailureAction: enforce 26 | status: 27 | autogen: {} 28 | ready: false 29 | rulecount: 30 | generate: 0 31 | mutate: 0 32 | validate: 0 33 | verifyimages: 0 34 | -------------------------------------------------------------------------------- /AWS/RDS/README.md: -------------------------------------------------------------------------------- 1 | # RDS 2 | 3 | |Validating Policy|Ref to standards|Status| 4 | |----| ----| ----| 5 | |rds-enhanced-monitoring-enabled|CA-7 SI-2|Complete| 6 | |rds-instance-default-admin-check|CM-2|Complete| 7 | |rds-instance-deletion-protection-enabled|CM-3 SC-5(2) SI-13(5)|Complete| 8 | |rds-instance-public-access-check|AC-3(7) AC-3 AC-4(21) AC-4 AC-6 AC-21 SC-7(3) SC-7(4) SC-7(9) SC-7(11) SC-7(16) SC-7(20) SC-7(21) SC-7|Complete| 9 | |rds-logging-enabled|AC-2(4) AC-4(26) AC-6(9) AU-2 AU-3 AU-6(3) AU-6(4) AU-10 AU-12 CA-7 CA-9(1) CM-3(6) IA-3(3) IR-4(12) SC-7(9) SC-7(10) SI-3(8) SI-4(20) SI-7(8)|Complete| 10 | |rds-multi-az-support|CP-6(2) CP-10 SC-5(2) SC-36 SI-13(5)|Complete| 11 | |rds-snapshot-encrypted||NOT SUPPORTED| 12 | |rds-snapshots-public-prohibited||NOT SUPPORTED| 13 | |rds-storage-encrypted|CA-9(1) CM-3(6) SC-7(10) SC-13 SC-28(1) SC-28 SI-7(6)|Complete| 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /AWS/Bootstrap/provider/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 4.20.1" 6 | } 7 | 8 | kubectl = { 9 | source = "gavinbunney/kubectl" 10 | version = "~> 1.14.0" 11 | } 12 | 13 | kubernetes = { 14 | source = "hashicorp/kubernetes" 15 | version = "~> 2.16.1" 16 | } 17 | 18 | helm = { 19 | source = "hashicorp/helm" 20 | version = "~> 2.6.0" 21 | } 22 | } 23 | 24 | required_version = ">= 1.2.3" 25 | } 26 | 27 | provider "aws" { 28 | profile = var.aws_profile 29 | region = var.aws_region 30 | } 31 | 32 | provider "kubectl" { 33 | config_path = "~/.kube/config" 34 | } 35 | 36 | provider "kubernetes" { 37 | config_path = "~/.kube/config" 38 | } 39 | 40 | provider "helm" { 41 | kubernetes { 42 | config_path = "~/.kube/config" 43 | } 44 | } -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/templates/AuthorisedRDSInstance.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: database.aws.crossplane.io/v1beta1 2 | kind: RDSInstance 3 | metadata: 4 | name: authorised-rdsinstance 5 | spec: 6 | forProvider: 7 | multiAZ: true 8 | cloudwatchLogsExportConfiguration: 9 | enableLogTypes: 10 | - postgresql 11 | deletionProtection: true 12 | masterUsername: collieadmin 13 | monitoringInterval: 1 14 | monitoringRoleArnRef: 15 | name: authorised-rds-role 16 | allocatedStorage: 20 17 | dbInstanceClass: db.t2.small 18 | engine: postgres 19 | engineVersion: "12" 20 | region: {{ .Values.region }} 21 | skipFinalSnapshotBeforeDeletion: true 22 | autoMinorVersionUpgrade: true 23 | storageEncrypted: true 24 | publiclyAccessible: false 25 | writeConnectionSecretToRef: 26 | namespace: crossplane-system 27 | name: rds-pw-out-authorised 28 | -------------------------------------------------------------------------------- /AWS/RDS/Policies/rds-instance-public-access-check.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: rds-instance-public-access-check 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - RDSInstance 17 | mutate: {} 18 | name: rds-instance-public-access-check 19 | validate: 20 | message: rds instance public access must be set to false. Set spec.forProvider.publiclyAccessible. 21 | pattern: 22 | spec: 23 | forProvider: 24 | publiclyAccessible: false 25 | validationFailureAction: enforce 26 | status: 27 | autogen: {} 28 | ready: false 29 | rulecount: 30 | generate: 0 31 | mutate: 0 32 | validate: 0 33 | verifyimages: 0 34 | -------------------------------------------------------------------------------- /AWS/RDS/Policies/rds-logging-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: rds-logging-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - RDSInstance 17 | mutate: {} 18 | name: rds-logging-enabled 19 | validate: 20 | message: rds logging must be enabled. Set spec.forProvider.cloudwatchLogsExportConfiguration. 21 | pattern: 22 | spec: 23 | forProvider: 24 | cloudwatchLogsExportConfiguration: 25 | enableLogTypes: '*' 26 | validationFailureAction: enforce 27 | status: 28 | autogen: {} 29 | ready: false 30 | rulecount: 31 | generate: 0 32 | mutate: 0 33 | validate: 0 34 | verifyimages: 0 35 | -------------------------------------------------------------------------------- /Tools/scripts/generateResources.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) 6 | ROOT_DIR=$(cd "$SCRIPT_DIR"/../.. && pwd) 7 | 8 | function generate_resources() { 9 | module_root=$(cd "$1"/.. && pwd) 10 | if [ ! -f "$module_root/resourceTemplates/values.secret.yaml" ] ; then 11 | cp "$module_root/resourceTemplates/values.secret.example.yaml" "$module_root/resourceTemplates/values.secret.yaml" 12 | fi 13 | helm template resource "$module_root/resourceTemplates" -f "$module_root/resourceTemplates/values.secret.yaml" --output-dir "$module_root/.tmp" 14 | rm -rf "$module_root/Resources" 15 | mkdir -p "$module_root/Resources" 16 | mv "$module_root"/.tmp/resourceTemplates/templates/* "$module_root/Resources/" 17 | rm -rf "$module_root/.tmp" 18 | } 19 | 20 | for dir in "$ROOT_DIR"/**/**/resourceTemplates; do 21 | generate_resources "$dir" 22 | done 23 | -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-bucket-versioning-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-bucket-versioning-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - s3.aws.crossplane.io/v1beta1/Bucket 17 | mutate: {} 18 | name: s3-bucket-versioning-enabled 19 | validate: 20 | message: s3 bucket versioning must be enabled. Set spec.forProvider.versioningConfiguration 21 | pattern: 22 | spec: 23 | forProvider: 24 | versioningConfiguration: 25 | status: Enabled 26 | validationFailureAction: enforce 27 | status: 28 | autogen: {} 29 | ready: false 30 | rulecount: 31 | generate: 0 32 | mutate: 0 33 | validate: 0 34 | verifyimages: 0 35 | -------------------------------------------------------------------------------- /AWS/RDS/Policies/rds-enhanced-monitoring-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: rds-enhanced-monitoring-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - database.aws.crossplane.io/v1beta1/RDSInstance 17 | mutate: {} 18 | name: rds-enhanced-monitoring-enabled 19 | validate: 20 | message: rds enhanced monitoring must be enabled . Set spec.forProvider.monitoringInterval 21 | pattern: 22 | spec: 23 | forProvider: 24 | monitoringInterval: 1 | 5 | 10 | 15 | 30 | 60 25 | validationFailureAction: enforce 26 | status: 27 | autogen: {} 28 | ready: false 29 | rulecount: 30 | generate: 0 31 | mutate: 0 32 | validate: 0 33 | verifyimages: 0 34 | -------------------------------------------------------------------------------- /AWS/RDS/Policies/rds-instance-deletion-protection-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: rds-instance-deletion-protection-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - database.aws.crossplane.io/v1beta1/RDSInstance 17 | mutate: {} 18 | name: rds-instance-deletion-protection-enabled 19 | validate: 20 | message: rds instance deletion protection must be enabled . Set spec.forProvider.deletionProtection 21 | pattern: 22 | spec: 23 | forProvider: 24 | deletionProtection: true 25 | validationFailureAction: enforce 26 | status: 27 | autogen: {} 28 | ready: false 29 | rulecount: 30 | generate: 0 31 | mutate: 0 32 | validate: 0 33 | verifyimages: 0 34 | -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-bucket-replication-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-bucket-replication-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - s3.aws.crossplane.io/v1beta1/Bucket 17 | mutate: {} 18 | name: s3-bucket-replication-enabled 19 | validate: 20 | message: 's3 bucket replication must be enabled. Set spec.forProvider.replicationConfiguration:' 21 | pattern: 22 | spec: 23 | forProvider: 24 | replicationConfiguration: 25 | ^(rules): 26 | - status: Enabled 27 | validationFailureAction: enforce 28 | status: 29 | autogen: {} 30 | ready: false 31 | rulecount: 32 | generate: 0 33 | mutate: 0 34 | validate: 0 35 | verifyimages: 0 36 | -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-version-lifecycle-policy-check.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-version-lifecycle-policy-check 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - s3.aws.crossplane.io/v1beta1/Bucket 17 | mutate: {} 18 | name: s3-version-lifecycle-policy-check 19 | validate: 20 | anyPattern: 21 | - spec: 22 | forProvider: 23 | lifecycleConfiguration: 24 | ^(rules): 25 | - status: Enabled 26 | message: s3 version lifecycle policy must be defined. Set spec.forProvider.notificationConfiguration 27 | validationFailureAction: enforce 28 | status: 29 | autogen: {} 30 | ready: false 31 | rulecount: 32 | generate: 0 33 | mutate: 0 34 | validate: 0 35 | verifyimages: 0 36 | -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-default-encryption-kms.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-default-encryption-kms 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - s3.aws.crossplane.io/v1beta1/Bucket 17 | mutate: {} 18 | name: s3-default-encryption-kms 19 | validate: 20 | message: 's3 bucket encryption with KMS must be enabled. Set spec.forProvider.serverSideEncryptionConfiguration::' 21 | pattern: 22 | spec: 23 | forProvider: 24 | serverSideEncryptionConfiguration: 25 | rules: 26 | - applyServerSideEncryptionByDefault: 27 | sseAlgorithm: aws:kms 28 | validationFailureAction: enforce 29 | status: 30 | autogen: {} 31 | ready: false 32 | rulecount: 33 | generate: 0 34 | mutate: 0 35 | validate: 0 36 | verifyimages: 0 37 | -------------------------------------------------------------------------------- /Tools/scripts/generatePolicies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) 6 | ROOT_DIR=$(cd "$SCRIPT_DIR"/../.. && pwd) 7 | LULA_PATH=${LULA_PATH:=$ROOT_DIR/Tools/lula/bin/lula} 8 | 9 | function rename() { 10 | for FILE in "$1/.lulapolicies"/* ; do 11 | filename=$(basename -- "$FILE") 12 | uuid="${filename%.*}" 13 | description=$(yq -r " .\"component-definition\".components[].\"control-implementations\"[].\"implemented-requirements\"[] | select(.uuid == \"$uuid\") | .description" "$2" | head -n1) 14 | arrIN=(${description//" "/ }) 15 | yq " ( .spec.validationFailureAction = \"enforce\" ) | ( .metadata.name = \"${arrIN[0]}\" )" "$FILE" > "$1"/Policies/"${arrIN[0]}".yaml 16 | done 17 | } 18 | 19 | for component_file in "$ROOT_DIR"/**/**/*-component-definition.yaml; do 20 | outpath=$(dirname "$component_file") 21 | $LULA_PATH generate "$component_file" -o "$outpath"/.lulapolicies 22 | mkdir -p "$outpath"/Policies 23 | rename "$outpath" "$component_file" 24 | rm -rf "$outpath"/.lulapolicies 25 | done -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-bucket-server-side-encryption-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-bucket-server-side-encryption-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - s3.aws.crossplane.io/v1beta1/Bucket 17 | mutate: {} 18 | name: s3-bucket-server-side-encryption-enabled 19 | validate: 20 | message: 's3 bucket encryption must be enabled. Set spec.forProvider.serverSideEncryptionConfiguration::' 21 | pattern: 22 | spec: 23 | forProvider: 24 | serverSideEncryptionConfiguration: 25 | rules: 26 | - applyServerSideEncryptionByDefault: 27 | sseAlgorithm: AES256 | aws:kms 28 | validationFailureAction: enforce 29 | status: 30 | autogen: {} 31 | ready: false 32 | rulecount: 33 | generate: 0 34 | mutate: 0 35 | validate: 0 36 | verifyimages: 0 37 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/AuthorisedBucket/AuthorisedBucketPolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1alpha3 2 | kind: BucketPolicy 3 | metadata: 4 | name: bucketpolicy-deny-http 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: bucketpolicy-deny-http-{{ .Values.suffix }} 9 | spec: 10 | forProvider: 11 | region: {{ .Values.region }} 12 | bucketNameRef: 13 | name: authorised-bucket 14 | policy: 15 | version: '2012-10-17' 16 | statements: 17 | - sid: "denyHTTP" 18 | action: 19 | - S3:GetObject 20 | principal: 21 | awsPrincipals: 22 | - iamRoleArn: "*" 23 | - iamUserArn: "*" 24 | effect: Deny 25 | condition: 26 | - operatorKey: Bool 27 | conditions: 28 | - key: "aws:SecureTransport" 29 | booleanValue: false 30 | resource: 31 | - "arn:aws:s3:::authorised-bucket-{{ .Values.suffix }}/*" 32 | -------------------------------------------------------------------------------- /AWS/Bootstrap/down.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | while getopts ":dp" o; do 6 | case "${o}" in 7 | d) 8 | CREATE_DEMO_RESOURCES="true" 9 | ;; 10 | p) 11 | KEEP_CLUSTER="true" 12 | ;; 13 | *) 14 | ;; 15 | esac 16 | done 17 | 18 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 19 | 20 | cd "$SCRIPT_DIR/cluster" 21 | OIDC_PROVIDER="$(terraform output -raw oidc_provider)" 22 | SUFFIX="$(terraform output -raw suffix)" 23 | 24 | if [ "$CREATE_DEMO_RESOURCES" == "true" ] 25 | then 26 | kubectl delete --ignore-not-found=true -R --wait -f "$SCRIPT_DIR/../S3/Resources" 27 | kubectl delete --ignore-not-found=true -R --wait -f "$SCRIPT_DIR/../RDS/Resources" 28 | cd "$SCRIPT_DIR/demoResources" 29 | terraform destroy -auto-approve -var="suffix=$SUFFIX" 30 | fi 31 | 32 | cd "$SCRIPT_DIR/provider" 33 | terraform destroy -auto-approve -var="oidc_provider=$OIDC_PROVIDER" -var="suffix=$SUFFIX" 34 | 35 | cd "$SCRIPT_DIR/crossplane" 36 | terraform destroy -auto-approve 37 | 38 | if [ "$KEEP_CLUSTER" != "true" ] 39 | then 40 | cd "$SCRIPT_DIR/cluster" 41 | terraform destroy -auto-approve 42 | fi 43 | -------------------------------------------------------------------------------- /AWS/RDS/resourceTemplates/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: resourceTemplates 3 | description: templates for generating demo resources 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "0.1.0" 25 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: resourceTemplates 3 | description: templates for generating demo resources 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "0.1.0" 25 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/NotificationTopic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: sns.aws.crossplane.io/v1beta1 2 | kind: Topic 3 | metadata: 4 | name: notification-bucket-topic 5 | spec: 6 | forProvider: 7 | displayName: notification-bucket-topic 8 | name: notification-bucket-topic 9 | region: {{ .Values.region }} 10 | policy: | 11 | { 12 | "Version": "2012-10-17", 13 | "Id": "example-ID", 14 | "Statement": [ 15 | { 16 | "Sid": "Example SNS topic policy", 17 | "Effect": "Allow", 18 | "Principal": { 19 | "Service": "s3.amazonaws.com" 20 | }, 21 | "Action": [ 22 | "SNS:Publish" 23 | ], 24 | "Resource": "arn:aws:sns:{{ .Values.region }}:{{ .Values.accountID }}:notification-bucket-topic", 25 | "Condition": { 26 | "ArnLike": { 27 | "aws:SourceArn": "arn:aws:s3:*:*:notification-bucket-{{ .Values.suffix }}" 28 | }, 29 | "StringEquals": { 30 | "aws:SourceAccount": "{{ .Values.accountID }}" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-bucket-logging-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-bucket-logging-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - s3.aws.crossplane.io/v1beta1/Bucket 17 | mutate: {} 18 | name: s3-bucket-logging-enabled 19 | validate: 20 | anyPattern: 21 | - spec: 22 | forProvider: 23 | loggingConfiguration: 24 | targetBucket: '*' 25 | - spec: 26 | forProvider: 27 | loggingConfiguration: 28 | targetBucketRef: 29 | name: '*' 30 | - spec: 31 | forProvider: 32 | loggingConfiguration: 33 | targetBucketSelector: 34 | matchLabels: '*' 35 | message: s3-bucket-logging must be enabled. Set spec.forProvider.loggingConfiguration 36 | validationFailureAction: enforce 37 | status: 38 | autogen: {} 39 | ready: false 40 | rulecount: 41 | generate: 0 42 | mutate: 0 43 | validate: 0 44 | verifyimages: 0 45 | -------------------------------------------------------------------------------- /AWS/S3/README.md: -------------------------------------------------------------------------------- 1 | # S3 2 | ## S3 Controls 3 | |Validating Policy|Ref to standards|Status| 4 | |----| ----| ----| 5 | |s3-bucket-level-public-access-prohibited| NIST 800-53r5 AC-3(7) AC-3 AC-4(21) AC-4 AC-6 AC-21 SC-7(3) SC-7(4) SC-7(9) SC-7(11) SC-7(20) SC-7(21) SC-7| Complete | 6 | |s3-bucket-logging-enabled.yaml| NIST 800-53r5 AC-2(4) AC-4(26) AC-6(9) AU-2 AU-3 AU-6(3) AU-6(4) AU-10 AU-12 CA-7 IA-3(3) IR-4(12) SC-7(9) SI-3(8) SI-4(20) SI-7(8)| Complete | 7 | |s3-bucket-public-read-prohibited|| N/A covered by s3-bucket-level-public-access-prohibited| 8 | |s3-bucket-public-write-prohibited| |N/A covered by s3-bucket-level-public-access-prohibited| 9 | |s3-bucket-replication-enabled| AU-9(2) CP-10 CP-6 CP-6(1) CP-6(2) CP-9 SC-36(2) SC-5(2) SI-13(5)| Complete | 10 | |s3-bucket-server-side-encryption-enabled| AU-9 CA-9(1) CM-3(6) SC-13 SC-28 SC-28(1) SC-7(10) SI-7(6)| Complete| 11 | |s3-bucket-ssl-requests-only| AC-17(2) AC-4 IA-5(1) SC-13 SC-23 SC-7(4) SC-8 SC-8(1) SC-8(2) SI-7(6)| Complete | 12 | |s3-bucket-versioning-enabled| AU-9(2) CP-10 CP-6 CP-6(1) CP-6(2) CP-9 SC-34(2) SC-5(2) SI-13(5)| Complete | 13 | |s3-default-encryption-kms| AU-9 CA-9(1) CM-3(6) SC-13 SC-28(1) SC-7(10)| Complete | 14 | |s3-event-notifications-enabled| CA-7 SI-3(8) SI-4 SI-4(4)| Complete | 15 | |s3-version-lifecycle-policy-check| AU-9(2) CP-6(2) CP-9 CP-10 SC-5(2) SI-13(5)| Complete | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/AuthorisedBucket/AuthorisedBucketTopic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: sns.aws.crossplane.io/v1beta1 2 | kind: Topic 3 | metadata: 4 | name: authorised-bucket-topic-{{ .Values.suffix }} 5 | spec: 6 | forProvider: 7 | displayName: authorised-bucket-topic-{{ .Values.suffix }} 8 | name: authorised-bucket-topic-{{ .Values.suffix }} 9 | region: {{ .Values.region }} 10 | policy: | 11 | { 12 | "Version": "2012-10-17", 13 | "Id": "example-ID", 14 | "Statement": [ 15 | { 16 | "Sid": "Example SNS topic policy", 17 | "Effect": "Allow", 18 | "Principal": { 19 | "Service": "s3.amazonaws.com" 20 | }, 21 | "Action": [ 22 | "SNS:Publish" 23 | ], 24 | "Resource": "arn:aws:sns:{{ .Values.region }}:{{ .Values.accountID }}:authorised-bucket-topic-{{ .Values.suffix }}", 25 | "Condition": { 26 | "ArnLike": { 27 | "aws:SourceArn": "arn:aws:s3:*:*:authorised-bucket-{{ .Values.suffix }}" 28 | }, 29 | "StringEquals": { 30 | "aws:SourceAccount": "{{ .Values.accountID }}" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-event-notifications-enabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-event-notifications-enabled 6 | spec: 7 | rules: 8 | - exclude: 9 | resources: {} 10 | generate: 11 | clone: {} 12 | cloneList: {} 13 | match: 14 | resources: 15 | kinds: 16 | - s3.aws.crossplane.io/v1beta1/Bucket 17 | mutate: {} 18 | name: s3-event-notifications-enabled 19 | validate: 20 | anyPattern: 21 | - spec: 22 | forProvider: 23 | notificationConfiguration: 24 | topicConfigurations: 25 | - events: '*' 26 | - spec: 27 | forProvider: 28 | notificationConfiguration: 29 | lambdaFunctionConfigurations: 30 | - events: '*' 31 | - spec: 32 | forProvider: 33 | notificationConfiguration: 34 | queueConfigurations: 35 | - events: '*' 36 | message: s3 event notifications must be enabled. Set spec.forProvider.notificationConfiguration 37 | validationFailureAction: enforce 38 | status: 39 | autogen: {} 40 | ready: false 41 | rulecount: 42 | generate: 0 43 | mutate: 0 44 | validate: 0 45 | verifyimages: 0 46 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: push 4 | 5 | jobs: 6 | lint-yaml: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: install yamllint 11 | run: sudo apt update && sudo apt-get install -y yamllint 12 | - name: run yamllint 13 | run: make yaml-lint 14 | lint-helm: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: azure/setup-helm@v3 19 | with: 20 | version: 'v3.10.3' 21 | - name: run helm lint 22 | run: make helm-lint 23 | lint-terraform: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | name: Checkout source code 28 | - uses: actions/cache@v3 29 | name: Cache plugin dir 30 | with: 31 | path: ~/.tflint.d/plugins 32 | key: tflint-${{ hashFiles('.tflint.hcl') }} 33 | - uses: terraform-linters/setup-tflint@v3 34 | name: Setup TFLint 35 | with: 36 | tflint_version: v0.45.0 37 | - uses: hashicorp/setup-terraform@v2 38 | - name: Init TFLint 39 | run: tflint --init 40 | env: 41 | # https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/plugins.md#avoiding-rate-limiting 42 | GITHUB_TOKEN: ${{ github.token }} 43 | - name: lint terraform 44 | run: make terraform-lint 45 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/ReplicationBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: replication-bucket 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: replication-bucket-{{ .Values.suffix }} 9 | spec: 10 | forProvider: 11 | acl: private 12 | locationConstraint: {{ .Values.region }} 13 | objectOwnership: BucketOwnerPreferred 14 | publicAccessBlockConfiguration: 15 | blockPublicAcls: true 16 | blockPublicPolicy: true 17 | ignorePublicAcls: true 18 | restrictPublicBuckets: true 19 | versioningConfiguration: 20 | status: Enabled 21 | replicationConfiguration: 22 | roleRef: 23 | name: dci-bucket-replication-role-{{ .Values.suffix }} 24 | rules: 25 | - deleteMarkerReplication: 26 | status: Disabled 27 | destination: 28 | bucket: {{ .Values.replication.bucketARN }} 29 | storageClass: STANDARD 30 | encryptionConfiguration: 31 | replicaKmsKeyId: "{{ .Values.replication.keyARN }}" 32 | filter: 33 | prefix: "" 34 | id: rule-1 35 | priority: 1 36 | sourceSelectionCriteria: 37 | sseKmsEncryptedObjects: 38 | status: Enabled 39 | status: Enabled 40 | -------------------------------------------------------------------------------- /Tools/loadcsv/Readme.md: -------------------------------------------------------------------------------- 1 | # Loading CSV Data 2 | 3 | A tool to aid bootstrapping compoent files from CSVs. 4 | 5 | ## Perquisites 6 | 7 | * Python 8 | * pyyaml - Install with `pip install -r requirements.txt` 9 | 10 | ## Usage 11 | 12 | `python loadcsv.py metadata.yaml nist.csv > out.yaml` 13 | 14 | The metadata file must contain the metadata for a component definition file, e.g. 15 | 16 | ```yaml 17 | component-definition: 18 | uuid: 20e597f6-836b-436f-ba4e-74be11f96d27 19 | metadata: 20 | title: collie-aws-s3-crossplane-community 21 | last-modified: '2023-01-05T12:00:00Z' 22 | version: "20230105" 23 | oscal-version: 1.0.0 24 | parties: 25 | - uuid: 98b53905-1ce2-4af0-a059-459b117925d1 26 | type: organization 27 | name: ControlPlane 28 | links: 29 | - href: values.secret.yaml && \ 27 | cd ../../RDS/resourceTemplates && \ 28 | yq eval '(.accountID = 1122334455667788)' \ 29 | values.secret.example.yaml > values.secret.yaml" 30 | - uses: azure/setup-helm@v3 31 | with: 32 | version: 'v3.10.3' 33 | - name: install kyverno 34 | run: | 35 | curl -LO https://github.com/kyverno/kyverno/releases/download/v1.9.2/kyverno-cli_v1.9.2_linux_x86_64.tar.gz && 36 | tar -xvf kyverno-cli_v1.9.2_linux_x86_64.tar.gz && 37 | mv kyverno /usr/local/bin 38 | - name: run tests 39 | run: make unit-test 40 | -------------------------------------------------------------------------------- /AWS/S3/bats-tests/test-require-versioning.bats: -------------------------------------------------------------------------------- 1 | setup_file() { 2 | load "${ROOT}/bats/test_helper/setup" 3 | _common_setup 4 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-bucket-versioning-enabled.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-bucket-versioning-enabled 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not having versioning" { 18 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-bucket-logging-enabled.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-bucket-logging-enabled 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not logging access" { 18 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-version-lifecycle-policy-check.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-version-lifecycle-policy-check 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not having a versioning policy" { 18 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 7 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 8 | export TESTNAME="${testfilename%.*}" 9 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/rds-multi-az-support.yaml" 10 | kubectl wait --for=condition=Ready clusterpolicy/rds-multi-az-support 11 | } 12 | 13 | setup() { 14 | load "${ROOT}/bats/test_helper/setup" 15 | _common_setup 16 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 17 | } 18 | 19 | @test "basic rds instance is blocked for not having multi az support" { 20 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-bucket-replication-enabled.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-bucket-replication-enabled 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not having replication" { 18 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 7 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 8 | export TESTNAME="${testfilename%.*}" 9 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/rds-instance-default-admin-check.yaml" 10 | kubectl wait --for=condition=Ready clusterpolicy/rds-instance-default-admin-check 11 | } 12 | 13 | setup() { 14 | load "${ROOT}/bats/test_helper/setup" 15 | _common_setup 16 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 17 | } 18 | 19 | @test "basic rds instance is blocked for using a default admin user" { 20 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 7 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 8 | export TESTNAME="${testfilename%.*}" 9 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/rds-logging-enabled.yaml" 10 | kubectl wait --for=condition=Ready clusterpolicy/rds-logging-enabled 11 | } 12 | 13 | setup() { 14 | load "${ROOT}/bats/test_helper/setup" 15 | _common_setup 16 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 17 | } 18 | 19 | @test "basic rds instance is blocked for not having logging enabled" { 20 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 7 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 8 | export TESTNAME="${testfilename%.*}" 9 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/rds-storage-encrypted.yaml" 10 | kubectl wait --for=condition=Ready clusterpolicy/rds-storage-encrypted 11 | } 12 | 13 | setup() { 14 | load "${ROOT}/bats/test_helper/setup" 15 | _common_setup 16 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 17 | } 18 | 19 | @test "basic rds instance is blocked for not encrypting storage" { 20 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 7 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 8 | export TESTNAME="${testfilename%.*}" 9 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/rds-instance-public-access-check.yaml" 10 | kubectl wait --for=condition=Ready clusterpolicy/rds-instance-public-access-check 11 | } 12 | 13 | setup() { 14 | load "${ROOT}/bats/test_helper/setup" 15 | _common_setup 16 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 17 | } 18 | 19 | @test "basic rds instance is blocked for not preventing public access" { 20 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-event-notifications-enabled.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-event-notifications-enabled 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not having event notifications enabled" { 18 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-bucket-ssl-requests-only.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-bucket-ssl-requests-only 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not using SSL only" { 18 | run patch_and_apply_yaml "$(cat < /dev/null && pwd ) 15 | 16 | cd "$SCRIPT_DIR/cluster" 17 | terraform apply -auto-approve 18 | aws eks update-kubeconfig --name "$(terraform output -raw cluster_id)" --alias collie --profile "$(terraform output -raw aws_profile)" --region "$(terraform output -raw aws_region)" 19 | OIDC_PROVIDER="$(terraform output -raw oidc_provider)" 20 | SUFFIX="$(terraform output -raw suffix)" 21 | 22 | cd "$SCRIPT_DIR/crossplane" 23 | terraform apply -auto-approve 24 | 25 | cd "$SCRIPT_DIR/provider" 26 | terraform apply -auto-approve -var="oidc_provider=$OIDC_PROVIDER" -var="suffix=$SUFFIX" 27 | 28 | if [ "$CREATE_DEMO_RESOURCES" == "true" ] 29 | then 30 | cd "$SCRIPT_DIR/demoResources" 31 | terraform apply -auto-approve -var="suffix=$SUFFIX" 32 | 33 | if [ ! -f "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.yaml" ] ; then 34 | cp "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.example.yaml" "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.yaml" 35 | fi 36 | 37 | if [ ! -f "$SCRIPT_DIR/../RDS/resourceTemplates/values.secret.yaml" ] ; then 38 | cp "$SCRIPT_DIR/../RDS/resourceTemplates/values.secret.example.yaml" "$SCRIPT_DIR/../RDS/resourceTemplates/values.secret.yaml" 39 | fi 40 | 41 | yq -i ".loggingBucket |= \"$(terraform output -raw logging_bucket_name)\"" "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.yaml" 42 | yq -i ".replication.bucketARN |= \"$(terraform output -raw replication_bucket_arn)\"" "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.yaml" 43 | yq -i ".replication.keyARN |= \"$(terraform output -raw replication_encryption_key_arn)\"" "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.yaml" 44 | yq -i ".accountID |= \"$(terraform output -raw account_id)\"" "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.yaml" 45 | yq -i ".suffix |= \"$SUFFIX\"" "$SCRIPT_DIR/../S3/resourceTemplates/values.secret.yaml" 46 | fi -------------------------------------------------------------------------------- /AWS/RDS/NIST_SP-800-53_rev5_RDS-baseline_profile.yaml: -------------------------------------------------------------------------------- 1 | profile: 2 | uuid: 0c04ddf1-a932-4e44-8208-8ec8dd9d1d2b 3 | metadata: 4 | title: NIST Special Publication 800-53 Revision 5 RDS BASELINE 5 | last-modified: 2021-06-08T13:57:33.585986-04:00 6 | version: Draft 7 | oscal-version: 1.0.0 8 | roles: 9 | - id: creator 10 | title: Document Creator 11 | - id: contact 12 | title: Contact 13 | parties: 14 | - uuid: 98b53905-1ce2-4af0-a059-459b117925d1 15 | type: organization 16 | name: ControlPlane 17 | email-addresses: 18 | - security@control-plane.io 19 | addresses: 20 | - addr-lines: 21 | - 483 Green Lanes 22 | city: London 23 | state: London 24 | postal-code: N13 4BS 25 | responsible-parties: 26 | - role-id: creator 27 | party-uuids: 28 | - 98b53905-1ce2-4af0-a059-459b117925d1 29 | imports: 30 | - href: NIST_SP-800-53_rev5_catalog.yaml 31 | include-controls: 32 | - with-ids: 33 | - ac-2.4 34 | - ac-21 35 | - ac-3 36 | - ac-3.7 37 | - ac-4 38 | - ac-4.21 39 | - ac-4.26 40 | - ac-6 41 | - ac-6.9 42 | - au-10 43 | - au-12 44 | - au-2 45 | - au-3 46 | - au-6.3 47 | - au-6.4 48 | - ca-7 49 | - ca-9.1 50 | - cm-2 51 | - cm-3 52 | - cm-3.6 53 | - cp-10 54 | - cp-6.2 55 | - ia-3.3 56 | - ir-4.12 57 | - sc-13 58 | - sc-28 59 | - sc-28.1 60 | - sc-36 61 | - sc-5.2 62 | - sc-7 63 | - sc-7.10 64 | - sc-7.11 65 | - sc-7.16 66 | - sc-7.20 67 | - sc-7.21 68 | - sc-7.3 69 | - sc-7.4 70 | - sc-7.9 71 | - si-13.5 72 | - si-2 73 | - si-3.8 74 | - si-4.20 75 | - si-7.6 76 | - si-7.8 77 | merge: 78 | as-is: true 79 | -------------------------------------------------------------------------------- /AWS/S3/Policies/s3-bucket-ssl-requests-only.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | creationTimestamp: null 5 | name: s3-bucket-ssl-requests-only 6 | spec: 7 | rules: 8 | - context: 9 | - apiCall: 10 | jmesPath: items[].spec.forProvider.bucketNameRef.name 11 | urlPath: /apis/s3.aws.crossplane.io/v1alpha3/bucketpolicies/ 12 | name: bucketnameref 13 | exclude: 14 | resources: {} 15 | generate: 16 | clone: {} 17 | cloneList: {} 18 | match: 19 | resources: 20 | kinds: 21 | - s3.aws.crossplane.io/v1beta1/Bucket 22 | mutate: {} 23 | name: s3-bucket-ssl-requests-only-check-policy-exists 24 | validate: 25 | deny: 26 | conditions: 27 | all: 28 | - key: '{{request.object.metadata.name}}' 29 | operator: AnyNotIn 30 | value: '{{bucketnameref}}' 31 | message: 'BucketPolicy must exist for created bucket: {{request.object.metadata.name}} existing bucket policies are in place for the following buckets: {{bucketnameref}} ' 32 | - exclude: 33 | resources: {} 34 | generate: 35 | clone: {} 36 | cloneList: {} 37 | match: 38 | resources: 39 | kinds: 40 | - BucketPolicy 41 | mutate: {} 42 | name: s3-bucket-ssl-requests-only-require-http-validate 43 | validate: 44 | message: All Bucket Policy must include deny http block 45 | pattern: 46 | spec: 47 | forProvider: 48 | policy: 49 | ^(statements): 50 | - action: 51 | - S3:GetObject 52 | condition: 53 | - conditions: 54 | - booleanValue: false 55 | key: aws:SecureTransport 56 | operatorKey: Bool 57 | effect: Deny 58 | sid: denyHTTP 59 | validationFailureAction: enforce 60 | status: 61 | autogen: {} 62 | ready: false 63 | rulecount: 64 | generate: 0 65 | mutate: 0 66 | validate: 0 67 | verifyimages: 0 68 | -------------------------------------------------------------------------------- /AWS/S3/resourceTemplates/templates/AuthorisedBucket/AuthorisedBucket.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: s3.aws.crossplane.io/v1beta1 2 | kind: Bucket 3 | metadata: 4 | name: authorised-bucket 5 | annotations: 6 | # This will be the actual bucket name. It must be globally unique, so you 7 | # probably want to change it before trying to apply this example. 8 | crossplane.io/external-name: authorised-bucket-{{ .Values.suffix }} 9 | spec: 10 | forProvider: 11 | objectOwnership: BucketOwnerPreferred 12 | notificationConfiguration: 13 | topicConfigurations: 14 | - ID: notification-one 15 | events: [s3:ObjectCreated:*] 16 | topicRef: 17 | name: authorised-bucket-topic-{{ .Values.suffix }} 18 | loggingConfiguration: 19 | targetBucket: {{ .Values.loggingBucket }} 20 | targetPrefix: logs 21 | acl: private 22 | locationConstraint: {{ .Values.region }} 23 | publicAccessBlockConfiguration: 24 | blockPublicAcls: true 25 | blockPublicPolicy: true 26 | ignorePublicAcls: true 27 | restrictPublicBuckets: true 28 | serverSideEncryptionConfiguration: 29 | rules: 30 | - applyServerSideEncryptionByDefault: 31 | sseAlgorithm: aws:kms 32 | versioningConfiguration: 33 | status: Enabled 34 | lifecycleConfiguration: 35 | rules: 36 | - abortIncompleteMultipartUpload: 37 | daysAfterInitiation: 42 38 | status: Enabled 39 | replicationConfiguration: 40 | roleRef: 41 | name: dci-bucket-replication-role-{{ .Values.suffix }} 42 | rules: 43 | - deleteMarkerReplication: 44 | status: Disabled 45 | destination: 46 | bucket: {{ .Values.replication.bucketARN }} 47 | storageClass: STANDARD 48 | encryptionConfiguration: 49 | replicaKmsKeyId: "{{ .Values.replication.keyARN }}" 50 | # key ref is broken, sets key id instead of ARN so AWS request fails 51 | # replicaKmsKeyIdRef: 52 | # name: dci-bucket-replication-key-{{ .Values.suffix }} 53 | filter: 54 | prefix: "" 55 | id: rule-1 56 | priority: 1 57 | sourceSelectionCriteria: 58 | sseKmsEncryptedObjects: 59 | status: Enabled 60 | status: Enabled 61 | -------------------------------------------------------------------------------- /AWS/S3/bats-tests/test-require-kms.bats: -------------------------------------------------------------------------------- 1 | setup_file() { 2 | load "${ROOT}/bats/test_helper/setup" 3 | _common_setup 4 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-default-encryption-kms.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-default-encryption-kms 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not having kms server side encryption" { 18 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-bucket-server-side-encryption-enabled.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-bucket-server-side-encryption-enabled 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked for not having server side encryption" { 18 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 7 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 8 | export TESTNAME="${testfilename%.*}" 9 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/rds-enhanced-monitoring-enabled.yaml" 10 | kubectl wait --for=condition=Ready clusterpolicy/rds-enhanced-monitoring-enabled 11 | } 12 | 13 | setup() { 14 | load "${ROOT}/bats/test_helper/setup" 15 | _common_setup 16 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 17 | } 18 | 19 | @test "basic rds instance is blocked for not having enhanced monitoring" { 20 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 7 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 8 | export TESTNAME="${testfilename%.*}" 9 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/rds-instance-deletion-protection-enabled.yaml" 10 | kubectl wait --for=condition=Ready clusterpolicy/rds-instance-deletion-protection-enabled 11 | } 12 | 13 | setup() { 14 | load "${ROOT}/bats/test_helper/setup" 15 | _common_setup 16 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 17 | } 18 | 19 | @test "basic rds instance is blocked for not having deletion protection enabled" { 20 | run patch_and_apply_yaml "$(cat </dev/null 2>&1 && pwd )" 5 | testfilename=$(basename -- "$BATS_TEST_FILENAME") 6 | export TESTNAME="${testfilename%.*}" 7 | apply_policy_with_label_selector "$TESTNAME" "$MODULE_ROOT/Policies/s3-bucket-level-public-access-prohibited.yaml" 8 | kubectl wait --for=condition=Ready clusterpolicy/s3-bucket-level-public-access-prohibited 9 | } 10 | 11 | setup() { 12 | load "${ROOT}/bats/test_helper/setup" 13 | _common_setup 14 | export MODULE_ROOT="$( cd "$(dirname "$BATS_TEST_FILENAME")/.." >/dev/null 2>&1 && pwd )" 15 | } 16 | 17 | @test "basic bucket is blocked" { 18 | run patch_and_apply_yaml "$(cat < 6 | 7 | Collie is a POC project demonstrating how infrastructure provisioned by cloud infrastructure controllers can be simultaneously secured and validated for compliance. It provides NIST 800-53 rev.5 aligned libraries of Kyverno Policy to secure Infrastructure provisioned by [Crossplane](https://www.crossplane.io/) [Community Provider](https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/), generated from OSCAL documents, and leverages Lula to use the same OSCAL documents to validate compliance. 8 | 9 | * [Collie](#collie) 10 | * [Why?](#why) 11 | * [Prerequisites](#prerequisites) 12 | * [AWS Usage](#aws-usage) 13 | * [Bootstrapping a cluster](#bootstrapping-a-cluster) 14 | 15 | 16 | ## Why? 17 | 18 | Organisations moving into cloud, often rely on using Infrastructure as Code templates with Policy enforcement for provisioning secure infrastructure, and rightly so. However, Kubernetes itself provisions infrastructure natively, through Load Balancer Services, or through third party cloud controllers, such as Crossplane. 19 | 20 | This provides a challenge in large regulated organisations, which invest heavily in IAC and Policy patterns, forming an integral part of their enterprise security architecture. This inertia and lack of an equivalent pattern for infrastructure provisioned by cloud controllers will impede the adoption of these technologies, despite the benefits of 21 | * developer simplicity via consumption of a single K8s deployment pipeline for apps and infrastructure 22 | * gitops enablement for infrastructure 23 | and 24 | * drift protection, as cloud controllers continuously reconcile 25 | 26 | The intention of Collie is to create confidence in a pattern where k8s provisioned infrastructure is secured via policy engines acting as Kubernetes Admission controllers, such as Kyverno, and can be simultaneously validated for compliance using OSCAL documents and Lula. 27 | 28 | For a more in depth discussion around the motivation for this and demonstrating it in action you can watch Andy presenting at Cloud Native SecurityCon 2023: 29 | 30 | [![SecurityCon Talk](https://img.youtube.com/vi/cvoWlwftbEE/0.jpg)](https://www.youtube.com/watch?v=cvoWlwftbEE) 31 | 32 | 33 | ## Prerequisites 34 | 35 | An installation of [lula](https://github.com/defenseunicorns/lula) is required for compliance validation and policy generation. For convenience, its installed as a submodule within [Tools](./Tools). Install lula by running `make build-lula` from the root folder. 36 | 37 | Kyverno also needs to be installed (Not via Kubectl plugin). Follow [Kyverno Installation Instructions](https://kyverno.io/docs/kyverno-cli/#manual-binary-installation) 38 | 39 | ## AWS Usage 40 | 41 | ### Bootstrapping a Cluster 42 | 43 | > ❗️Note that this cluster is not hardened and is for experimentation/test purposes only ❗️ 44 | 45 | > ❗ The cluster API will be exposed publicly but will be restricted so it can only be accessed from the IP address you are running the tests from. This can cause issues if your IP address changes, e.g. you move to a new network. If this happens you will have to manually add your new IP from the AWS console. ❗ 46 | 47 | Navigate to [AWS/Bootstrap](https://github.com/controlplaneio/collie/tree/main/AWS/Bootstrap) 48 | 49 | To create a cluster first run `init.sh` to pull the terraform dependencies for both stages. 50 | 51 | Within [provider](./AWS/Bootstrap/provider), [cluster](./AWS/Bootstrap/cluster) and [demoResources](./AWS/Bootstrap/demoResources/) you need to define the variables (via tfvars) `project_name`. Optionally you can also define `aws_region` and `aws_profile` 52 | 53 | Then to bring up the cluster run `up.sh`. Once this is complete you should have a cluster with Kyverno, Crossplane and the Crossplane Community Provider ready to go. 54 | 55 | ### Applying Policies 56 | 57 | Policies are available within the [AWS/S3/Policies](https://github.com/controlplaneio/collie/tree/main/AWS/S3/Policies) and [AWS/RDS/Policies](https://github.com/controlplaneio/collie/tree/main/AWS/RDS/Policies) folders 58 | 59 | They are set with `validationFailureAction: enforce` and can be applied to the cluster to block non-compliant resources. 60 | 61 | ### Creating Resources 62 | 63 | Resources are available within [AWS/S3/resourceTemplates](https://github.com/controlplaneio/collie/tree/main/AWS/S3/resourceTemplates) and [AWS/RDS/resourceTemplate](https://github.com/controlplaneio/collie/tree/main/AWS/RDS/resourceTemplates) folders 64 | 65 | They leverage helm, set the secrets values file as required, prior to installation. 66 | 67 | To install individual resources use 68 | `helm template -f values.secret.yaml -s templates/.yaml . | kubectl apply -f -` 69 | 70 | ### Assessing for Compliance 71 | 72 | Lula can be run against the cluster to assess for compliance, using the component definitions as a source of truth. run `/lula validate ` 73 | 74 | ### Creating new Policies 75 | 76 | 1. Edit the relevant OSCAL component definition file e.g [AWS/S3/S3-component-definition.yaml](https://github.com/controlplaneio/collie/blob/main/AWS/S3/S3-component-definition.yaml) 77 | 2. Run `make generate-policies` from the root directory. Policies will be written to the Policies folder for each collection (e.g. S3, RDS, etc) 78 | 3. Update the tests by creating the resources required within the `resourceTemplates` folder and updating `kyverno-test.yaml` 79 | 80 | ### Unit Testing 81 | 82 | Tests are defined within kyverno-test.yaml with resources tested defined within resourceTemplates for each service 83 | 84 | To run the tests define `values.secret.yaml` in each resourceTemplates folder and run `make unit-test` 85 | 86 | ### Linting 87 | 88 | To ensure code quality code will be linted ot make sure all files follow a consistent style and catch any potential errors. 89 | 90 | Currently there are linters for: 91 | 92 | * Yaml ([yamllint](https://github.com/adrienverge/yamllint)) 93 | * Helm ([Helm CLI](https://helm.sh/)) 94 | * Terraform ([Terraform CLI](https://www.terraform.io/), [tflint](https://github.com/terraform-linters/tflint)) 95 | 96 | These will be run as part of CI, it is recommended to install these on your system and check linting before contributing code. 97 | 98 | ### End to End testing 99 | 100 | End to end testing involves deploying a k8s cluster with crossplane and kyverno installed then deploying the kyverno policies and then trying to create infrastructure and asserting that the resources are appropriately allowed or denied by kyverno. 101 | 102 | > ❗️Note that these tests are creating real resources in AWS. This may take a while, as some resources like RDS can take 10-15 minutes to provision, this means it can appear that the tests are hanging but it may take 20 mins for them to complete. Since the resources are actually provisioned this may incur costs.❗️ 103 | 104 | > ❗️ These tests can sometimes be a little brittle, normally rerunning will fix it. We are targeting to make them less brittle by the 1st of May❗️ 105 | 106 | The tests are run using [bats](https://bats-core.readthedocs.io/en/stable/index.html). The bats tooling is included as submodules so make sure to run `git submodule update --init --recursive` to pull the submodules. 107 | Tests are run concurrently which requires [GNU Parallel](https://www.gnu.org/software/parallel/) to be installed on your system as well. 108 | 109 | You will also need to be set up to bootstrap a cluster, this requires [terraform](https://www.terraform.io/) to be installed, any tfvars described in the bootstrapping section of this readme, the terraform dependencies are installed (this can be done with the `init.sh` script), and you need credentials for connecting to the cloud provider e.g. AWS or GCP. 110 | Also make sure the correct profile is configured for AWS in a `tfvars` file in *each* folder, i.e. `cluster`, `demoResources` and `provider`. 111 | 112 | Some of the tests require [yq](https://github.com/mikefarah/yq) so this should be installed. 113 | 114 | The policies and test resources will be regenerated before running the tests. To generate the test resources you need [helm](https://helm.sh/) installed. To generate the policies you need to build [lula](https://github.com/defenseunicorns/lula) which requires [go](https://go.dev/) to be installed. The Lula repository is included as a submodule so you don't need to clone it. 115 | 116 | Once all this is set up you can run the tests by running `make bats-test`. This will provision the cluster and also tear it down when the tests are done. If you are going to be running the tests multiple times you can set the environment variable `PRESERVE_CLUSTER` to true to skip deleting the cluster at the end as creating a cluster can take over 10 minutes. To skip refreshing the terraform state each time as well you can set `SKIP_TERRAFORM` to `true` as well. 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 control-plane.io 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /v4_uuids.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 89d25434-b4f9-411a-a260-7d48415fb646 46 | 31a0f235-067b-4db8-94d6-fcc34f90c4ec 47 | f4158849-fd8f-4cd4-bef7-ff03df1e9cdb 48 | 25f18394-9fdb-42f3-81b9-4e12882326fc 49 | 661d30d1-0298-4e99-bf35-a8159c982522 50 | ae45ee3f-8642-40d5-b201-bab2acf0c1df 51 | 1f18da07-364f-4c48-abea-e2fa0acd6ccc 52 | 59c8e3b8-0839-42f1-8f5d-2f68dbc56694 53 | fa481bd8-ef84-4118-909d-3100b77e7c7d 54 | ce74517f-87e9-4562-8689-4870e158e1fe 55 | 71de99cb-741d-4f67-a425-8ef213435a53 56 | 1291d740-10d9-4d1c-b27b-676e477fce02 57 | 65840b2f-79b6-4127-9860-8faa7e3a6965 58 | 7c560899-b90a-4c6e-92eb-5fdc6b870b23 59 | 30691057-2911-40f9-a5eb-da731cf39549 60 | f06e773e-d482-4547-bb4e-2240958df9c4 61 | 2836f9cb-01a4-4a75-be58-394036968a25 62 | 8aca14cc-c4a3-437f-8b89-d9a8461581c0 63 | f8f0d8ec-bd78-4754-bff4-6d035ed4d42d 64 | 8c8f2629-955a-413a-b4fb-406e55035af2 65 | c6361ea6-c5c3-48a9-a004-df98e2d46608 66 | 3983f7e9-a185-478d-9c06-086650af1e13 67 | 4fb21453-720d-4a84-a181-e325b4f08c82 68 | af6a5479-a029-47c3-a58a-4f689d3d8b89 69 | 1f6af470-843d-4282-a7b6-1eef824cd2ee 70 | 9255581e-ea4a-43ff-81e4-e969ff6be777 71 | 88281bbe-b9e3-40d6-9c23-372eb07830e4 72 | 0afec872-1e5e-4b5a-8781-de1ae7fd6fa1 73 | ebda5962-fdb3-4422-a360-ab757602a46d 74 | e9c22e6b-a87b-454a-a164-fb0ba73c2e87 75 | 6b638b71-1bdb-41fb-8ecd-50c70c4f560f 76 | e2433800-bbd4-4393-a425-b189ce01153c 77 | 4f9d808b-5c44-4cba-b07e-7422dd17e0e5 78 | d4f445ee-544f-4195-998a-ba91c2ea662c 79 | 2da37ec7-d25b-4eef-9382-66c0d49ffb43 80 | 7a9bcdb7-4e54-4b14-afb7-0f9e7e9ef1f0 81 | 4b36533c-9ab1-4a31-bc79-de6e2dc0e174 82 | 23959129-05a4-4616-9613-c46fabcaade3 83 | 1fd110ab-c784-4efa-9cd9-3683a1370dc4 84 | aad449e4-7ced-4444-9993-f5e4bd4516d3 85 | ce36001f-c075-48cd-929a-5152880710e8 86 | e900c687-2afb-4aee-aaf9-aba36b983730 87 | 4d089833-e9a5-4e0a-ba1a-796937c43dd7 88 | 4b2c2b8a-14d1-4043-a3e2-2c4b475bf0a6 89 | 5016fa43-6193-4226-a9ac-d02769c9aec2 90 | 5d7839ca-02da-46eb-afbd-6c7f625986f9 91 | 1062265d-7283-4e59-a70f-f0789680848a 92 | c7d44fe0-8e5a-4059-b254-f8f37a862dd7 93 | d31ce077-762b-4012-bdf3-f03bf91320ef 94 | 44510890-851f-4cb3-a882-da9d7ea1f191 95 | 6e9faad8-1ba9-4908-bd02-a59ad5889cc5 96 | 104baa8b-20eb-4162-b0bf-3797816e08de 97 | 385f091a-ee42-4f4c-bd88-aca4be390e36 98 | a8e79ca8-f832-486a-afdb-32fbb5d8d2f4 99 | be54cfb4-0879-4218-8296-23cd60260d6e 100 | d02e6fbd-58ab-41fb-8a45-c5c4bb49e31a 101 | 333023b7-3c4b-469e-877f-71b2b5e3bc4f 102 | e0eabebb-b2b3-494c-9b27-d3df81dc10fe 103 | 12e7fb7b-45f4-43fd-8e41-6d16a5706850 104 | 13fbf274-fc2b-4284-b739-2ce76b478101 105 | 67058523-22e7-4c77-8423-766712635e9f 106 | 438723a7-4156-494b-b0a6-275fd9c2e697 107 | 41a94c24-b6e6-4a9d-9e24-04a6d1b66547 108 | f01766de-4f29-4130-9081-0e076cc155aa 109 | 1c91439a-194f-466c-89cb-c7c0691ae8f0 110 | 999d3e3e-baf4-488e-b6a7-6cbe6006e963 111 | 4e322a0b-d5e1-4a4f-99d4-41ed8b691eab 112 | 9397cdaa-4d60-4436-84e9-32a9b529d110 113 | e737fa35-c088-48f9-9208-34f6bbec9170 114 | bcfb7eb5-381f-42d3-a2c1-e91ce80a0b29 115 | 8d2c44b7-9979-4742-832d-dd6594324ca1 116 | 99c7f7cf-afba-4b44-a4ea-4e396e3f205e 117 | 6f43c641-be5a-4940-8264-3a0f1897f3c4 118 | 7247e697-1715-49a1-b8d4-e0d1f1158060 119 | 682e2b87-3e2e-4810-8396-018e73663af9 120 | a2de695d-ce58-49a7-9cd4-e8dcdf225942 121 | 206bc5b4-379c-444c-85bf-08b8b801b176 122 | b5f03cf7-9f72-49c6-a2fa-362fad08ee0d 123 | 1b1bb635-9a57-4db1-8e7b-55570f8d4f65 124 | 75abc75e-e4ef-4261-9ddc-a8a140cb6b0e 125 | ce3f5d64-0812-4429-8f9c-dcab897b07d9 126 | a92244eb-4da8-4cee-89b7-f9fd3a409d63 127 | bdfc10f2-88b9-4eb6-838d-eb960310f184 128 | e75a0dca-f026-41e7-9bb7-2393ead1af55 129 | 8cc9e9a2-703f-443f-9083-7b769b57fb6c 130 | e0bbb7ca-29fa-438a-be7f-658235308321 131 | b332776d-ebd2-4849-a609-266fc86f13bd 132 | 89129078-fb2d-44c3-aa88-64209192a2e5 133 | fb82b509-a8c3-407d-97a6-b3069fa6257b 134 | 9cf4d531-13d0-4cca-a2e4-61d1fbe48334 135 | a4cba7d9-0a25-4def-80e2-12e77b3bb67a 136 | cc7ef95e-b7db-46c7-96a3-e3bfa3cce749 137 | 504dea3a-ca75-4c61-8003-f4a91f4b6f0c 138 | 1f884954-118c-4b4a-9488-ce4435a13837 139 | 91279b70-f974-48b9-baf4-6a8d85e525f9 140 | ee7fc744-8a3c-448e-bb0e-0015a3a41b2a 141 | c3f7ef8a-7726-4867-8817-19e5c51ac67a 142 | e08ca2e2-0e9c-40fa-904a-12ea434a3a54 143 | 5895ca0c-ef13-4841-aa34-794da37e6d55 144 | e9c67b95-ca03-4302-9798-42fb413ef68e 145 | 43be66bc-24b6-4048-99a9-b2d705657c0d 146 | a7610e0b-4602-42d8-8c80-458cf106bb5d 147 | 9c4e622e-e102-4625-a6dc-d855500d0d94 148 | 2e1be9b6-a12f-4a86-a3e3-6c031d87beb2 149 | 719d6785-c1d2-46f4-a640-ea5b0e714ad4 150 | 2a9a549a-9836-4d11-a20b-0eb1b4b6783a 151 | e4e13b85-214b-4430-8a6c-96c186c4e7b4 152 | 98869163-5ad9-42f9-a507-48c72fd5bf16 153 | ff15abff-cb39-4c48-86dc-1e30d55fc36f 154 | 2d7f7513-9ba3-44ad-a721-b3238a675a2e 155 | 1703a073-fe21-4968-82f2-72c20694f017 156 | 5dbae9c7-815d-42ec-afd0-e9cc24e9e4ac 157 | a85d95e1-b74e-4026-8d27-7c5aeec8a9b6 158 | 690231d5-d5e6-4f07-817b-d3359d668f9c 159 | 506cab80-3533-4bf5-8717-3ad797fe64bc 160 | 7ba85484-8333-4f8a-80bf-f72b41ac2655 161 | 88e9d7a9-22d2-4b8c-b0a2-980d50e14bcc 162 | 5145698c-1f2d-4a16-87f5-4c25baf01f98 163 | 4e8d1934-9b6b-4bbb-a9de-c105a5af83f7 164 | 97756e57-6cb0-4ceb-97e4-3069a905f502 165 | e9a2093d-12b4-42b5-8dc9-34d4fca177e9 166 | ca11cfff-b28b-43a4-b718-d7be28e9b6e1 167 | b83333fd-3e06-4a80-adc9-0c369849ad23 168 | 7c64b9e1-e544-402b-9b2a-58f469f78b4a 169 | ba815ae1-9efc-4f76-806d-cb5189230778 170 | 328b8646-34f6-4728-a9b1-40a692dea78e 171 | ed3e42bb-b33d-48a4-8b08-aa38f5984d09 172 | 7c710223-edf6-4be8-862b-cac0dd41c2f6 173 | b1c2b610-f3fc-4bce-b13b-e5eecc91bb90 174 | 8c0eaa9f-f7c0-4ea2-8a2b-172867bdaf05 175 | 1dfa3eab-e9d3-4ab2-812c-c778147cb628 176 | 16f0608b-ef3e-4a1b-9453-fdb8532c866a 177 | 87492eca-dd35-4e7b-8c9f-fae70fa76d85 178 | ba13bab4-985a-4269-97c1-2f5e4df424a6 179 | 867ab100-4fc8-48ff-9cf9-91ece01bde68 180 | 9728c7aa-204c-4bf1-b822-00771b236882 181 | a49ee812-a298-46e5-9f9c-791d89480aa5 182 | 9374773b-2e06-4552-a270-db33e0a57a86 183 | dff948b9-5196-4c37-8963-804491515535 184 | bb60c70a-122a-46e4-8d1d-0989b674e9b4 185 | 95a25083-e1e6-4ca4-8ece-0e4295b60f90 186 | 675e754a-e672-436b-8453-1d2df192b48d 187 | f3453a1b-9a00-4e8d-8bbc-8b097b9e1f34 188 | adaf5493-25a1-421e-814a-946936387a94 189 | 8df5e3c2-7339-44cf-bc05-78e75658e862 190 | acebb7c9-44cf-4d95-a393-d9f0ff881a94 191 | 1991f382-5683-423d-956f-a0bbcf865f06 192 | c33a2081-3290-4b58-a7ee-eddcef023fd2 193 | 19231aad-c883-42b5-a512-d4d0c1d59ae8 194 | 7b3af999-b96d-49e6-b26c-681c7d3d1cf5 195 | b2d26842-9078-45d1-98ca-8bdb272d34f4 196 | 69b27550-09a5-40df-b31c-55dc80f1b4ef 197 | e85ecfc2-8bd2-497a-bca4-3f53c4ae17c4 198 | c6db83cf-4311-4b7a-b979-3efb130764c9 199 | 14c207e8-83f5-4da3-b296-2ee91e562ee7 200 | 5cae3bf0-2306-4213-8b86-5a9009be74f6 201 | 00ded546-5b6d-44f8-8130-ca137a3edc0d 202 | 00ae6d97-ba51-4df4-b887-229842df6cce 203 | 19f73fe3-bb3b-4aed-ac34-9da71afba13e 204 | 80c318e0-c16a-4a95-8af6-7657fba1868e 205 | 67b96654-4a79-4aeb-a7e2-cd972084e73f 206 | c269a3c1-3de7-450e-905d-7220332f0762 207 | 4bd3c03f-48e1-4d0a-b1e2-be0e624b0bc1 208 | 99dbcb03-57f1-48cc-aab0-49817eceb6bc 209 | fbcf73b0-8053-4286-bed1-fb6e43940319 210 | 6c101ea4-8846-4f7d-9419-e7fdf87453d6 211 | 453b51d6-5cf0-40f8-98be-58fcfe4f557f 212 | 1725d6f5-cafd-4efd-ad84-d5b28914da94 213 | 839a73ce-4cd8-4a3e-9638-ad73ea60651f 214 | 943203f4-a728-4d19-9aea-ecb7f72dc436 215 | 856bf32a-dab2-416b-8567-ea30408c6da3 216 | 4ef5d8b7-b702-40ca-a2bb-bfc68c1aff3f 217 | 8dfc95f7-a27b-43cb-81b5-224b2e79d76a 218 | 9426d6ec-c81c-42f2-99d5-4459308fc2b4 219 | 1385058c-3a67-4299-8587-9ec671b5f1f7 220 | 9a6af534-8e36-47e5-a77a-1fc0ce93ee27 221 | 5903828b-97b5-47bf-809b-9bc4940bc713 222 | 42f47c8d-8569-4d3f-baf3-c4ff8e0fb28e 223 | c2917929-2a1f-4e18-8af0-cbfdd9209115 224 | 2456641f-b192-4dde-8f21-b5eceefb7e68 225 | 20fd490d-d120-4e8d-ae45-d2e781895735 226 | eed7fda0-c46c-4adc-b325-f60d3210ccf1 227 | 2c1e68e1-6189-4b02-b8bb-3f4cf16cb652 228 | 29c3ca79-6a81-4fd3-97b5-f63ca441d3a6 229 | 59e3d212-f65a-4c81-8609-0e22009b34bb 230 | a8960b81-e649-4b8d-a4d8-b83b5105485b 231 | 5109c193-76c0-4a8c-be87-e1984f9b546a 232 | e804cc24-7aab-4492-883c-c9f23c399c9e 233 | 589a7686-e95c-4739-980b-df6e6505181f 234 | 89c7418f-e104-4748-a092-eec7b53eedc6 235 | 59088a8d-6953-4022-aee8-c6031ad3fbf7 236 | 9f8e13af-40e6-44a5-b241-4fed4b4b897b 237 | 89a4d96e-5b51-49b4-8365-51d85a56a1a7 238 | 0d862679-a916-4ded-a9c5-07231c0d744d 239 | 82c4ec6e-81d1-46c3-9aea-a5976dd7ba68 240 | 552e58da-0439-45bf-927d-b8a7048c1d82 241 | 402e3d1e-54f5-4739-9e20-746061da37e2 242 | 68b7fe92-88d6-4744-bd67-4c5925c7303b 243 | 25c073cd-0cdb-47a4-981c-264a7a0c0c80 244 | c1b1652d-9fb7-4b79-a506-920c95ef7194 245 | 7f0c9825-3aaf-4a93-a4c8-1b0d6d7c7ca4 246 | 2d740b06-30e1-49bb-b702-727bffde92e7 247 | 59657d9f-26df-4d5f-baa3-3ec6b970aeda 248 | 598bd5de-2500-463a-aafa-6bda64bd2ba0 249 | f7478de0-af81-4161-8afb-9a35a55a9359 250 | dd99a94c-c276-4b94-9d8d-e21442e6632d 251 | 50eaca0b-a679-474c-be1c-e6fcca3eb0bd 252 | b9ebd573-816f-4a32-832f-faff8482a506 253 | 91d19802-6df4-407c-b919-fe74fbb3aa62 254 | 9f630fef-6601-45ef-aec2-1d8ad7e74d88 255 | 86301525-18e7-48cd-b83e-37e64d1d40ad 256 | 68ac4e84-cd42-4723-a0db-b11d0056e6bd 257 | 219bb938-26a2-4d2e-9a8d-cc1a210740e2 258 | 5b9ce749-a245-4f0b-8c62-cbc0702e492b 259 | a0aec43e-f3c4-43d7-9bd0-b916a83e237a 260 | 89613825-4003-4f7b-b4bd-b49c80f244d6 261 | 1661ab58-bb45-4649-9de3-0532940fe488 262 | d01daa1e-dbe5-4421-a657-3cb5a86bc1ee 263 | c1ffd936-df1f-46e7-af75-0ab190d2c185 264 | ea021da4-6c28-423e-9fcd-ef9724bce962 265 | 94a41082-a461-490b-88db-275e19aee794 266 | 16bed4ba-0e4b-4a3d-9f5e-cb448ab42f49 267 | 6397be16-4e97-40c1-9d21-2f09f1100986 268 | a367abe3-63c9-4759-9049-4aa10451ca0e 269 | 60149727-56d4-4168-ba17-8e8d5e7b7f96 270 | 26b3cc19-a2e3-4c76-8779-84a71a6575b3 271 | d0258f92-1975-40b0-bf02-8bccaa9683fa 272 | 9c8a5300-b6f1-485c-b296-b4064f8dfce0 273 | e1d6173c-9fbf-46c0-8eb4-730050142f3b 274 | 772b5d36-a207-44d1-ae9b-8f6c54b818a6 275 | 850021a4-b6b3-4d63-80dc-378406adbab3 276 | 25c076ca-310e-400d-820b-27b05d455723 277 | d7dd59a4-ff75-4d65-ad83-1e7e0091899e 278 | d2e3612e-1118-48db-a861-00ba687247d5 279 | 09ab7f96-1a7f-4c5f-8dc6-9cef8fcad734 280 | f9430b88-3874-4d5d-bda8-ec7e2afb26d6 281 | 4add443f-1255-4bc6-a5b1-75b46c259c7e 282 | c38e1add-e388-4393-9f47-f8ba1ad3c2a5 283 | 6b21110a-36fc-4b6f-93e8-082647dc6046 284 | 1fdf9ffd-265a-4b3e-8e94-cfb4f353ef9b 285 | 72fd6977-0ad8-4876-a239-b81823d519e5 286 | 92fe2881-9c94-490d-a200-bab66ed4e346 287 | 4d1cfd99-aa59-4376-9d7d-76d2c53125ee 288 | 015e8409-7263-4aae-8374-e2d14854f23b 289 | a1eba1ad-ec3c-456b-9e14-39518b346329 290 | e982be89-5e8e-40d7-aea6-1b132ae03109 291 | 31b19c89-2178-4fa0-8163-8a48f0e91949 292 | 5d7d6baa-6451-4059-8241-2b75956ee41f 293 | ccca1ee1-ff19-4997-8095-088d845a6b61 294 | fa6f218a-1ba6-4738-b86d-47a030327caa 295 | ae677b23-866e-49ad-9a9c-6ba8d7daa8cb 296 | aef694e1-86a7-4a8a-8cf1-57fb6f343736 297 | 42320cd3-a8f8-41e0-97b0-9867205e05f3 298 | 5086d973-4b57-4111-af0b-985fbcc813ef 299 | c5d8ae31-6099-45de-a691-bfcf6e3c39dc 300 | 95ac6f6d-9a45-464a-ba0c-627710310f4f 301 | ec3f0b1f-c18f-4255-a9c7-519a3403acc2 302 | d0ba51b9-14ce-471d-b343-b5e783c42a35 303 | 31634dc7-9325-4cfb-8142-01bd32f407d4 304 | 199041a0-c3ba-4727-92b7-481fe0f801fa 305 | a4cc78a5-5817-4541-92b7-d657e0d73e47 306 | 7a43b1b3-b7cd-4025-a355-8bfca6dc949d 307 | 488a9be5-0fcd-4a02-b674-737f066b7d8c 308 | 41d8b3fd-5d38-43ce-86f8-38b8bacc8a58 309 | 525b0f90-55bb-4903-8228-a853e7e11b16 310 | 08bfc69b-125e-4ca8-b114-12af67b92831 311 | 166caa28-81cb-4d39-876c-b6573105c0cd 312 | 9b7cf31a-a18d-4cf5-8535-b6eabf8ed86e 313 | fdf3c9f4-da42-42ce-97e3-9d7d72fcab64 314 | f5a0532c-d172-4817-82aa-28cab51cfd19 315 | 7dc0a73d-8cd6-4a2e-922d-9b76888b145c 316 | 70bc63af-2d9b-4213-bcf0-3f892537e548 317 | 2b676ad3-46c8-42dc-9ce9-f18b4dc15d6c 318 | 46be2912-c929-41d4-98c6-fb94c0794fad 319 | 83a76854-76e3-48eb-b4dc-76da2c685bb5 320 | 4901f6ea-89a0-4eae-a213-071b92510e86 321 | 6a72072d-54a5-4407-b4e9-1cd257e1f04e 322 | f48e891f-cd77-4435-9642-1708f5cea11b 323 | 9799f62e-992d-41de-93d8-b2bf6a2f3428 324 | 5c96fce9-acd7-44fd-8083-c876cbd32717 325 | 8244ab9f-d500-4203-96db-4a1f22d71b5f 326 | 05d85ab4-15ad-450c-9578-4b2cae2ce3aa 327 | e7fab069-c309-4992-bee1-c32da6856cbf 328 | f2a02106-96db-4b76-b9d4-2e6c9a5ade4d 329 | dbe2f32a-953c-4353-a627-e43736bf3f68 330 | 82861eec-96e9-4230-8628-baff3b236a5c 331 | 3e7597e0-5805-4cb5-ae26-a9aa9408b63e 332 | 0b17ae69-3e61-44a0-817e-e9c994bbb106 333 | 066af033-fb74-43f5-9966-be2d11df7d77 334 | b1443d88-9844-42e4-958c-ba578019e743 335 | 6dde95f9-7958-4bc0-a755-c9d8c6c88422 336 | 182d44fb-aff5-4384-9356-35c96bd3350b 337 | ce7d38d6-66a0-4447-b5f1-5e9fe8231204 338 | 4b4e4a70-5ce5-4b19-bfca-73a2c5838625 339 | 791b3204-46ab-4059-b978-c9db86610e75 340 | 179d295f-676c-483a-9ccd-85c5b7aea40b 341 | 33430e99-3a28-4ea0-a86c-feccb7e07c5d 342 | 2ba23826-6cbc-483f-b343-8aa40843877c 343 | bbeaf01b-70a7-4242-b822-ecb6ff958e12 344 | c88c35e0-f326-4736-9aa0-56ef484a38fb 345 | cb24af9f-0eb9-4391-8d02-5d884950a1e2 346 | 26a0fcec-4cfa-4246-94b1-7e147a8adb4c 347 | 958c0f08-1cf3-43c8-8ba2-7c30b1e3b0ae 348 | 62a92209-8fcd-4ec0-9f3c-64ec47e7b150 349 | 5677d11c-ec7b-4304-afa9-8be6bc1a8454 350 | fdda4d52-df8e-4ea2-a8a6-1ee0daca4f63 351 | 0e1bacbd-7a49-4def-b877-0c9131892fe5 352 | ff0001d7-5840-40ff-affa-2965e2507471 353 | ef92073e-f9a7-4e71-b374-5521daa63027 354 | c8f921f2-a785-4c70-9b96-ee8acdd6bd47 355 | b2c26413-b123-4207-95f8-9c80764e692e 356 | b3472fd7-1614-4044-b990-a3c3630ae149 357 | e5c638c1-a662-4a6e-b1e6-a5fa2b908839 358 | c6a8381c-0c08-4bb0-89c9-252795c54710 359 | 4da999fb-00cb-44cb-bd0c-4dcec41e4a6d 360 | c9154343-1e80-403b-9b27-3e30469f597d 361 | 2cf7c86e-016e-411e-8af4-d9d79d01669b 362 | edc12fd1-8dce-4adc-aea5-f3420d001c3c 363 | 814db439-40b1-4c7e-a2e5-8c53e42be090 364 | 7a642259-98cf-4e4a-8e37-3e791e30b728 365 | 94faf904-405b-405a-afc9-a2ae745b1837 366 | 51207a29-eb2e-4ba3-a9aa-d1a693f584d6 367 | 16c12e37-3aa9-4955-b54a-41a5b1ea1e27 368 | a5e49159-b62e-4707-960c-5c555cc9344c 369 | cc7c30f4-fe2c-45cf-98cd-1ffe1403c8bc 370 | 9540fec3-9be5-47b0-84bc-18cd6c55241d 371 | c5fd77f6-69e9-462e-aaf1-6c1b9fed1b43 372 | c70b51b8-11f5-4a53-9f6c-506082fb6a8f 373 | 3208bbc0-d19c-4f64-b114-cf47fe047aaa 374 | d3b0d43a-5ad6-49f5-b1f0-9cca3923ce8c 375 | 097c4b29-5238-4ca3-a5b0-27bbd2d5d1bd 376 | fe349084-ca69-4cde-abcf-720f072ab13c 377 | ac296a4a-3697-47c4-af29-46b554e05f24 378 | 09652f75-ef60-4a3d-96d2-dad4a931c03d 379 | 22bf3312-03a3-428b-b7e0-aa2bb706c374 380 | 5ae78969-7718-47df-a81b-0594a983eb1e 381 | 353750f5-c5dc-4e54-b72c-e78af3ef2c50 382 | 4b61d6bb-35d8-4408-8045-24ac7ea5293d 383 | 86196365-cf61-40a6-a074-c9936d0b44f5 384 | 60937fd4-e081-47a2-b7fe-c0c24dae2a73 385 | b6da8cd6-db08-465a-820f-9373afcc1fce 386 | 287f79b3-4acd-4100-84f2-d6146e5cf22d 387 | 4b037af9-c1a4-46d1-a81c-f513a611ba32 388 | 41aaa50e-73a0-4ba9-be48-d960136af9e5 389 | f512cce5-1d6d-4eab-bf5e-bbbcd9b270f4 390 | 4309acb2-d538-4620-94af-c7295366c318 391 | 54f9b632-c34f-43bc-a473-dc094a781e89 392 | 9d94f003-09fe-4fbe-aa05-b43b5e1cb287 393 | 940c68cd-7d34-4c57-9c77-2c62d3183100 394 | c2476567-7632-4148-baf7-6d7410691e41 395 | 86ef1d1d-25ee-48cd-a414-7b7744a2c0d2 396 | ad57e7d7-fe0c-44cd-a680-41e874b49838 397 | e29a522a-2ff3-4f89-bed0-c12496890950 398 | 2bc71fd7-9930-4782-9965-8c7810bb194d 399 | c67628a3-d621-4c41-940d-822d1aa56fae 400 | 182e1c36-4a67-418f-a28a-2c8acd4e908e 401 | 2a0cc3f7-8f04-4dfe-a007-d428a1701078 402 | 2165ec06-55d4-46e5-9ce5-ff4a73f0a2bd 403 | f103371d-885d-4cef-84d9-25169ba58a89 404 | e80f11ba-a1c3-4168-87b5-2d550f339566 405 | 08edabbd-d7a7-4a48-b032-84d4fa08cf09 406 | e4c5b470-4576-451a-8cc4-94f4b0cde0af 407 | 8d8b6a54-68c1-41d6-b0b9-d2c2448df454 408 | 0d4b1e61-4550-466e-8deb-17af288a40e9 409 | e15f0afb-4a9f-4163-a462-64b4cc0534bc 410 | 081514d6-ae4d-413d-bd68-c71a1a86efbe 411 | 3e8fb014-5d8c-4e23-aec4-d935d390f4c7 412 | abcc551c-348c-4059-96f6-a32fc1e29ca0 413 | 48a04803-f822-448b-8f81-e3d5b4d4ffc5 414 | 91326b37-5989-4528-b2f1-4939db9b14e0 415 | aa28e198-112a-468a-981c-9ed86dddc941 416 | 40508721-71ea-431a-a723-f1dad623a59c 417 | 029c38ec-5478-439c-9a7c-85b1427eb4b2 418 | 2fb4b12b-4b25-4a5c-a4b9-78c26a6ca211 419 | 957035b0-677a-4a1d-876a-d521f0684fe3 420 | f3f1b359-8ec1-4898-8dfd-649ecc1b6f5f 421 | a4d7d62b-3500-4b6c-ba9b-7217c0e18995 422 | 53e36447-f759-4084-8a53-0733fe08ffba 423 | cfd07cf9-99b9-4b20-9c93-3a394740993b 424 | 15a228ba-4501-4e43-b0c7-fc486b35738b 425 | 1a3c9a39-a240-40cf-9f62-e9e430402458 426 | bb390dec-5caa-43da-9455-d59f7e032f6b 427 | 5ff511f3-5b29-407d-8ed0-9ee40b6b0c84 428 | a5393fc7-1e35-45f3-b887-ddb612b1ec50 429 | 9c284069-e6eb-45e7-a08f-f5abbf6a7700 430 | 8fe719d4-3cff-44f5-9122-e24746aa9da1 431 | 60bcfaf5-0f1f-4e11-9e8c-b26fb4396f90 432 | 0568f842-aa65-4a4c-96c2-71143986044d 433 | 4da16e3a-2ab3-4ae6-89fb-5bd84b167264 434 | d8c4c484-7dc1-47d7-8e69-8572770226f6 435 | f366ae9f-085d-4d38-994a-58252464dcdb 436 | 535a3f6c-b1e4-4595-a050-4c33edc955c8 437 | 38e224f2-95b2-4370-9ad8-1e907b50bf0c 438 | be0a0abf-b7cb-4442-99eb-cfe939b6480a 439 | 527bc01f-c59a-4f65-afb9-13caa90cf183 440 | e3e30c36-70cc-49d3-994d-ec7ca40382cc 441 | c5bc02d1-0246-46a0-92bf-16c0daecccd6 442 | 560fa21b-3acb-4478-b618-f246d8840db6 443 | e9b790e1-972d-47ec-a2da-13c372c5c3d2 444 | e6d4892b-42ca-4d62-adbc-ac4676f6bf0a 445 | 71c77741-3de8-4940-a298-d0b487f262ca 446 | 42c43894-a4e9-42b2-a06b-c55f394c9779 447 | 97d421ac-7aee-4759-a8c4-ca8551e3e3b1 448 | 5627a187-3cfe-46e1-a587-ac63b0bcbfa1 449 | 1c56e82d-75f1-4546-a7b5-3a308d30b3fa 450 | 24b623f2-4254-48ce-a881-3ac04309bf42 451 | 0224f3c8-8569-4379-8921-b8b60f13e48d 452 | 02c5a8c8-8f52-49f2-8b6a-8391d4b1e43f 453 | 33e613e8-f04f-41ff-8637-f3a387361ce2 454 | 88b99350-7ebd-42e2-a08f-038bd57d53d3 455 | 955d518a-6ae0-4b07-8729-5796517b1770 456 | 9b6933bb-73b7-43d2-b94f-7e0cc2aaa0c8 457 | 8942ea29-a5e6-4434-a2ea-ed7ac7f27ea3 458 | 64dcb7fe-9d92-43af-87e2-1e8119e89727 459 | 068b5c0b-0085-42c3-b757-a5cd6c6abd98 460 | 794c560a-1e04-49ac-8028-49be9bd000dc 461 | 5cbcbf5a-34ba-4bf1-a205-564993791009 462 | 4f55cd0d-6265-4b3c-b386-ae18439269c3 463 | b139bb38-7d3c-4e56-a4c2-c075c791732f 464 | a5c0b1d7-0193-43c7-a746-a850d06ba69b 465 | 0ac0d742-14af-46e8-a1f4-083aac4a387d 466 | 2d9fcf39-bb42-4058-8c15-44560bb5c215 467 | 60ed7091-b1bb-4f22-8f9d-782d6fffe7f7 468 | 6f4a9006-0700-4e02-aa17-c82585d2d594 469 | 5d64acf5-93b8-408e-8008-07566c21398d 470 | d280bf98-b35a-4eb6-a84c-364abfec805e 471 | 16bc03c1-652f-474d-a4a9-7325e66fe7f7 472 | b323a632-47a4-4090-80fb-5b3772b94bcc 473 | dbc5650c-e40f-4783-92c2-993d85bd6557 474 | 2d2a790b-5729-4d1a-a476-5d0fddb454f1 475 | 717ff1ad-5b30-420a-b8be-b3fca66d1da4 476 | 784958ea-15af-40bf-9163-11de3b20501e 477 | 58887b26-2dd0-4d58-ba1f-f1d0fc237354 478 | be478226-801e-48cb-9b89-885e14048715 479 | 804fd7a6-97e7-4bc3-aba3-84b0483c0898 480 | fbaa078a-87f4-442d-b848-265358ed6c1e 481 | aea46ae9-9448-46f1-beaf-5610ccbcf314 482 | def50a23-2e99-440c-bc72-602e170e7ed4 483 | b417364c-2d5a-4ed1-a0f7-f6f34d7bfbb0 484 | bdbdfced-7b6b-4a5b-a72d-e8f993022147 485 | 0b9c29e1-6a00-447a-9eb0-cbde4f842ca8 486 | 64419317-5d1f-46d4-b81b-0237ac06e1d2 487 | 748ea428-fca2-4df0-9fcf-ad7591685c10 488 | 4d41ef44-d314-41da-abf4-25ef46834dd0 489 | 150a4558-d9ee-404d-bdcf-53daa190ab7c 490 | 248f7f26-17a6-4498-8256-47086a43b66c 491 | 860e0cdb-c9e7-4b8e-8408-94c69a9ef220 492 | ca94c526-c9b4-45e9-a2ec-ee1d9be94a1a 493 | 21b936cc-b9ab-4e1a-98a4-a26188cd5ce2 494 | 391a5c32-239e-4d51-b51f-f4a0f6205d78 495 | 1d1e8c66-2ae5-4338-9f70-b89013d90cb0 496 | 56367318-29fd-43af-9b1f-93e66a92242f 497 | 83b344f7-7dae-454d-90bb-0335067b1faf 498 | 259ce25a-8e15-423f-99c2-f2207dffc338 499 | 733cd63b-5bd2-4f56-abcd-4b8cc03ccfb8 500 | 43f1d72f-ea05-4ec6-a383-1780c109bbe0 --------------------------------------------------------------------------------