├── .actrc ├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE ├── release-drafter.yml └── workflows │ ├── bump-version.yml │ ├── ci.yml │ ├── publish.yml │ ├── python-dependency-updater.yml │ ├── release-drafter.yml │ └── update-azure-data.yml ├── .gitignore ├── .gitmodules ├── .pylintrc ├── .readthedocs.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING-ARCHIVED.md ├── HomebrewFormula └── cloud-guardrails.rb ├── LICENSE ├── MANIFEST.in ├── Makefile ├── Pipfile ├── README.md ├── SECURITY.md ├── cloud_guardrails ├── __init__.py ├── bin │ ├── __init__.py │ ├── cli.py │ └── version.py ├── command │ ├── __init__.py │ ├── create_config_file.py │ ├── create_parameters_file.py │ ├── describe_policy.py │ ├── generate_terraform.py │ ├── list_policies.py │ └── list_services.py ├── iam_definition │ ├── __init__.py │ ├── azure_policies.py │ ├── parameter.py │ ├── policy_definition.py │ └── properties.py ├── scrapers │ ├── __init__.py │ ├── azure_docs.py │ ├── compliance_data.py │ ├── parse_builtin_definitions.py │ └── standard.py ├── shared │ ├── __init__.py │ ├── config.py │ ├── data │ │ ├── azure_security_benchmark.html │ │ ├── ccmc-l3.html │ │ ├── cis_benchmark.html │ │ ├── compliance-data.csv │ │ ├── compliance-data.json │ │ ├── compliance-data.xlsx │ │ ├── hipaa-hitrust-9-2.html │ │ ├── iam-definition.json │ │ ├── iso-27007.html │ │ ├── new-zealand-ism.html │ │ ├── nist-sp-800-171-r2.html │ │ └── nist-sp-800-53-r4.html │ ├── parameters_categorized.py │ ├── utils.py │ └── validate.py ├── templates │ ├── __init__.py │ ├── config-template.yml.j2 │ ├── config_template.py │ ├── parameters-template.yml.j2 │ └── parameters_template.py └── terraform │ ├── __init__.py │ ├── guardrails.py │ ├── no-parameters │ └── policy-initiative-no-params.tf.j2 │ ├── provider │ └── provider.tf.j2 │ ├── terraform_no_params.py │ ├── terraform_with_params.py │ └── with-parameters │ └── policy-initiative-with-parameters.tf.j2 ├── docs ├── cheatsheet.md ├── contributing │ └── contributing.md ├── custom.css ├── images │ ├── demo-480.gif │ ├── demo-480.mov │ ├── demo.gif │ ├── demo.mov │ └── example-output.png ├── index.md ├── installation.md ├── requirements-docs.txt ├── summaries │ ├── 0-all.md │ ├── 0-no-params.md │ ├── README.md │ ├── all_policies.csv │ ├── no-params.csv │ ├── no-params.md │ ├── params-optional.csv │ ├── params-optional.md │ ├── params-required.csv │ └── params-required.md └── tutorials │ ├── basic-key-vault.md │ ├── basic.md │ ├── parameters-optional.md │ ├── parameters-required.md │ └── policy-selection-config.md ├── examples ├── parameters-config-example.yml ├── parameters-optional-default.yml ├── parameters-required-default.yml ├── terraform-demo-no-params │ ├── NP-all-table.csv │ ├── NP-all-table.md │ ├── no_params.tf │ ├── provider.tf │ └── terraform.tf ├── terraform-demo-params-optional │ ├── PO-all-table.csv │ ├── PO-all-table.md │ ├── params_optional.tf │ ├── provider.tf │ └── terraform.tf └── terraform-demo-params-required │ ├── PR-all-table.csv │ ├── PR-all-table.md │ ├── params_required.tf │ ├── provider.tf │ └── terraform.tf ├── mkdocs.yml ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── test ├── command │ ├── enforce │ │ └── params_optional_storage.tf │ ├── no_params.tf │ ├── parameters-optional-enforce.yml │ ├── parameters-optional.yml │ ├── params_optional.tf │ ├── params_required.tf │ ├── params_required_kubernetes.tf │ ├── test_create_parameters_file.py │ ├── test_describe_policy.py │ ├── test_generate_terraform.py │ ├── test_list_policies.py │ └── test_list_services.py ├── files │ ├── 2019-09-01 │ │ └── Microsoft.Authorization.json │ ├── ASC_Storage_DisallowPublicBlobAccess_Audit.json │ ├── AllowedUsersGroups.json │ ├── ApiManagement_AllowedVNETSkus_AuditDeny.json │ ├── Automation_AuditUnencryptedVars_Audit.json │ ├── AzureMonitoring_AddSystemIdentity_Prerequisite.json │ ├── Cosmos_DisableMetadata_Append.json │ ├── GuestConfiguration_WindowsCertificateInTrustedRoot_Deploy.json │ ├── PrivateLink_PublicNetworkAccess_Modify.json │ ├── SqlDBAuditing_Audit.json │ ├── bad-config.yml │ ├── config-match-keywords.yml │ ├── example-config.yml │ ├── kubernetes_parameters.yml │ ├── no_params.tf │ ├── params_optional.tf │ ├── params_required.tf │ ├── policy_id_pairs_kv.json │ └── stash │ │ ├── test_parameters_config.py │ │ ├── test_services_statistics.py │ │ ├── test_terraform.py │ │ └── test_terraform_v2.py ├── iam_definition │ ├── test_azure_policies.py │ ├── test_gh_92_params.py │ ├── test_parameter.py │ ├── test_policy_definition.py │ └── test_properties.py ├── shared │ ├── test_config.py │ └── test_parameters_categorized.py ├── templates │ └── test_parameters_template.py └── terraform │ ├── test_python_hcl2.py │ ├── test_terraform_guardrails.py │ ├── test_terraform_no_params.py │ └── test_terraform_with_params.py ├── update_compliance_data.py ├── update_iam_definition.py └── utils ├── combine_csvs.py ├── terraform-demo.sh └── update-policy-table.sh /.actrc: -------------------------------------------------------------------------------- 1 | -P ubuntu-latest=nektos/act-environments-ubuntu:18.04 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | charset = utf-8 11 | 12 | [*.yml.j2] 13 | indent_size = 2 14 | 15 | 16 | [*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | # Docstrings and comments use max_line_length = 79 21 | [*.py] 22 | max_line_length = 119 23 | 24 | [docs/**.txt] 25 | max_line_length = 79 26 | 27 | [*.{tf,tfvars}] 28 | indent_size = 2 29 | indent_style = space 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | cloud_guardrails/shared/data/* linguist-vendored 2 | cloud_guardrails/shared/azure-policy/* linguist-vendored 3 | HomebrewFormula/* linguist-vendored 4 | Makefile linguist-vendored 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code Owners 2 | # Defines individuals or teams that are responsible for code in a repository. 3 | # More info about CODEOWNERS file - https://help.github.com/articles/about-codeowners/ 4 | 5 | # These owners will be the default owners for everything in the repo. 6 | # Unless a later match takes precedence, these users will be requested 7 | # for review when someone opens a pull request. 8 | * @kmcquade 9 | #ECCN:Open Source -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | ## What does this PR do? 2 | 3 | [//]: # (Required: Describe the effects of your pull request in detail. If 4 | multiple changes are involved, a bulleted list is often useful.) 5 | 6 | ## What gif best describes this PR or how it makes you feel? 7 | 8 | [//]: # (Encouraged: Insert an appropriate gif/meme to brighten up your reviewer's day.) 9 | 10 | ## Completion checklist 11 | 12 | - [ ] Additions and changes have unit tests 13 | - [ ] Unit tests, Pylint, security testing, and Integration tests are passing. GitHub actions does this automatically 14 | - [ ] The pull request has been appropriately labeled using the provided PR labels 15 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: '$RESOLVED_VERSION 🌈' 2 | tag-template: '$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - 'quick-fix' 14 | - title: '🧰 Maintenance' 15 | label: 16 | - 'chore' 17 | - 'maintenance' 18 | - 'maintain' 19 | - 'cleanup' 20 | - title: '📝 Documentation' 21 | label: 22 | - 'documentation' 23 | - 'docs' 24 | 25 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 26 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 27 | version-resolver: 28 | major: 29 | labels: 30 | - 'major' 31 | minor: 32 | labels: 33 | - 'minor' 34 | patch: 35 | labels: 36 | - 'patch' 37 | default: patch 38 | template: | 39 | ## Changes 40 | 41 | $CHANGES 42 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | bump-version: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | ref: main 13 | submodules: recursive 14 | - name: Bump version 15 | run: | 16 | version_file="cloud_guardrails/bin/version.py" 17 | 18 | # https://github.com/bridgecrewio/checkov/blob/master/.github/workflows/build.yml#L87-L132 19 | git config --local user.email "action@github.com" 20 | git config --local user.name "GitHub Action" 21 | git fetch --tags 22 | git pull origin main 23 | latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) 24 | echo "latest tag: $latest_tag" 25 | new_tag=$(echo $latest_tag | awk -F. -v a="$1" -v b="$2" -v c="$3" '{printf("%d.%d.%d", $1+a, $2+b , $3+1)}') 26 | echo "new tag: $new_tag" 27 | 28 | printf "# pylint: disable=missing-module-docstring\n__version__ = '$new_tag'""" > $version_file 29 | 30 | git commit -m "Bump to ${new_tag}" $version_file || echo "No changes to commit" 31 | git push origin 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: continuous-integration 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ['3.7'] 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v2 14 | with: 15 | submodules: recursive 16 | 17 | - name: Setup Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | 22 | - name: Setup Terraform 23 | uses: hashicorp/setup-terraform@v1 24 | with: 25 | terraform_version: 0.12.28 26 | 27 | - name: Install dependencies 28 | run: make setup-dev 29 | 30 | - name: Run bandit (security test) 31 | run: make security-test 32 | 33 | - name: Run pytest (unit tests) 34 | run: make test 35 | 36 | - name: Validate Terraform output 37 | run: make terraform-validate 38 | 39 | - name: Install the package to make sure nothing is randomly broken 40 | run: make install 41 | 42 | # - name: pylint 43 | # run: | 44 | # make lint 45 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: ['3.7'] 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | submodules: recursive 17 | 18 | - name: Setup Python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - name: Install dependencies 24 | run: make setup-dev 25 | 26 | - name: Run bandit (security test) 27 | run: make security-test 28 | 29 | - name: Run pytest (unit tests) 30 | run: make test 31 | 32 | - name: Validate Terraform output 33 | run: make terraform-validate 34 | 35 | - name: Install the package to make sure nothing is randomly broken 36 | run: make install 37 | 38 | publish-package: 39 | needs: test 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v2 43 | with: 44 | submodules: recursive 45 | - name: Set up Python 3.7 46 | uses: actions/setup-python@v2 47 | with: 48 | python-version: 3.7 49 | - name: Install dependencies 50 | run: | 51 | pip install -r requirements.txt 52 | pip install -r requirements-dev.txt 53 | - name: create python package 54 | run: | 55 | git config --local user.email "action@github.com" 56 | git config --local user.name "GitHub Action" 57 | git fetch --tags 58 | git pull origin main 59 | pip install setuptools wheel twine 60 | python -m setup sdist bdist_wheel 61 | - name: Publish package 62 | uses: pypa/gh-action-pypi-publish@master 63 | with: 64 | user: __token__ 65 | password: ${{ secrets.PYPI_PASSWORD }} 66 | 67 | update-brew: 68 | needs: publish-package 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v2 72 | with: 73 | submodules: recursive 74 | - name: Set up Python 3.7 75 | uses: actions/setup-python@v2 76 | with: 77 | python-version: 3.7 78 | - name: publish brew 79 | run: | 80 | sleep 5m 81 | git config --local user.email "action@github.com" 82 | git config --local user.name "GitHub Action" 83 | pip install homebrew-pypi-poet 84 | pip install cloud-guardrails -U 85 | git fetch origin 86 | git checkout --track origin/main 87 | latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) 88 | echo "latest tag: $latest_tag" 89 | git pull origin $latest_tag 90 | mkdir -p "HomebrewFormula" && touch "HomebrewFormula/cloud-guardrails.rb" 91 | poet -f cloud-guardrails > HomebrewFormula/cloud-guardrails.rb 92 | git add . 93 | git commit -m "update brew formula" cloud_guardrails/bin/cli.py HomebrewFormula/cloud-guardrails.rb || echo "No brew changes to commit" 94 | git push -u origin main 95 | 96 | bump-version: 97 | needs: update-brew 98 | runs-on: ubuntu-latest 99 | steps: 100 | - uses: actions/checkout@v2 101 | with: 102 | ref: main 103 | submodules: recursive 104 | 105 | - name: Bump version 106 | run: | 107 | version_file="cloud_guardrails/bin/version.py" 108 | 109 | # https://github.com/bridgecrewio/checkov/blob/master/.github/workflows/build.yml#L87-L132 110 | git config --local user.email "action@github.com" 111 | git config --local user.name "GitHub Action" 112 | git fetch --tags 113 | git pull origin main 114 | latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) 115 | echo "latest tag: $latest_tag" 116 | new_tag=$(echo $latest_tag | awk -F. -v a="$1" -v b="$2" -v c="$3" '{printf("%d.%d.%d", $1+a, $2+b , $3+1)}') 117 | echo "new tag: $new_tag" 118 | 119 | printf "# pylint: disable=missing-module-docstring\n__version__ = '$new_tag'""" > $version_file 120 | 121 | git commit -m "Bump to ${new_tag}" $version_file || echo "No changes to commit" 122 | git push origin 123 | -------------------------------------------------------------------------------- /.github/workflows/python-dependency-updater.yml: -------------------------------------------------------------------------------- 1 | name: python-dependency-updater 2 | 3 | on: 4 | # Run on the first day of the month 5 | schedule: 6 | - cron: '0 0 1 * *' 7 | 8 | jobs: 9 | python-dependency-updater: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | python-version: ['3.7'] 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | submodules: recursive 18 | - name: Setup Python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - name: Run Pyup.io Dependency updater 24 | run: | 25 | pip install pyupio 26 | pip install -r requirements.txt 27 | default_branch=`git remote show origin | grep 'HEAD branch' | cut -d' ' -f5` 28 | pyup --provider github --provider_url https://api.github.com --repo=$GITHUB_REPOSITORY --user-token=${{ secrets.PYUP_GITHUB_ACCESS_TOKEN }} --branch $default_branch --initial 29 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: [ main, master ] 6 | 7 | jobs: 8 | update_release_draft: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # Drafts your next Release notes as Pull Requests are merged into "master" 12 | - uses: release-drafter/release-drafter@v5 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/update-azure-data.yml: -------------------------------------------------------------------------------- 1 | name: Update Azure Policy Data 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 1 * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update-azure-data: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: recursive 15 | 16 | - name: Setup Python 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: 3.7 20 | 21 | - name: Update the Git submodule with the latest changes from Azure Policies 22 | run: make update-submodule-with-merge 23 | 24 | - name: Pull the updates from the Git submodule 25 | run: | 26 | git config --local user.email "action@github.com" 27 | git config --local user.name "GitHub Action" 28 | cd cloud_guardrails/shared/azure-policy 29 | current_commit=$(git rev-parse --short HEAD) 30 | echo "current commit for Azure Policy repo: $current_commit" 31 | git pull origin master 32 | latest_commit=$(git rev-parse --short HEAD) 33 | echo "latest commit for Azure Policy repo: $current_commit" 34 | cd ../../../ 35 | echo "This should update Azure Policy submodule from ${current_commit} ${latest_commit}" 36 | 37 | - name: Update IAM Definition 38 | run: make update-iam-definition 39 | 40 | - name: Update Compliance Data 41 | run: make update-compliance-data 42 | 43 | - name: Update Policy Tables 44 | run: make update-policy-table 45 | 46 | - name: Set outputs 47 | id: vars 48 | run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" 49 | 50 | - name: PR if files were updated 51 | uses: peter-evans/create-pull-request@v3 52 | with: 53 | commit-message: Update database 54 | title: 'Updates database' 55 | body: This is an automated PR created because Azure Policy resources were updated. 56 | branch: ${{ steps.vars.outputs.sha_short }} 57 | delete-branch: true 58 | labels: | 59 | automation 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | .idea 3 | .vscode 4 | 5 | # Mac OS X 6 | .DS_Store 7 | 8 | # Serverless framework 9 | .serverless 10 | .requirements.zip 11 | node_modules/ 12 | 13 | # Working directories 14 | tmp 15 | !.gitkeep 16 | 17 | # Python 18 | .coverage 19 | .Python 20 | __pycache__/ 21 | *.py[cod] 22 | *$py.class 23 | env/ 24 | build/ 25 | develop-eggs/ 26 | dist/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | lib/ 31 | lib64/ 32 | parts/ 33 | sdist/ 34 | var/ 35 | venv/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # HashiCorp 53 | **/.terraform/* 54 | *.plan 55 | *.tfstate 56 | *.tfstate.* 57 | *.tfvars 58 | override.tf 59 | #!terraform.tfvars 60 | .vagrant 61 | packer_cache/ 62 | *.box 63 | 64 | #### Other Repository specific 65 | provider.tf 66 | .notes/* 67 | tmp/* 68 | research.py 69 | 70 | # Local testing 71 | cloud-guardrails-* 72 | utils/private-terraform-demo.sh 73 | 74 | # Terraform testing 75 | examples/terraform-testing/ 76 | **/state.tf 77 | 78 | # Excel sheets 79 | **/~$*.xlsx 80 | 81 | # Local testing yml files 82 | exclusions.yml 83 | config.yml 84 | /parameters.yml 85 | /parameters-*.yml 86 | 87 | # Generated files 88 | /params_required.tf 89 | /params_optional.tf 90 | /no_param*.tf 91 | /params*.tf 92 | NP-*.csv 93 | RP-*.csv 94 | PR-*.csv 95 | PO-*.csv 96 | NP-*.md 97 | RP-*.md 98 | PO-*.md 99 | PR-*.md 100 | !examples/terraform-demo-no-params/NP-all-table.* 101 | !examples/terraform-demo-params-optional/PO-all-table.* 102 | !examples/terraform-demo-params-required/PR-all-table.* 103 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cloud_guardrails/shared/azure-policy"] 2 | path = cloud_guardrails/shared/azure-policy 3 | url = https://github.com/Azure/azure-policy.git 4 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | mkdocs: 8 | configuration: mkdocs.yml 9 | fail_on_warning: false 10 | 11 | # Optionally build your docs in additional formats such as PDF and ePub 12 | formats: all 13 | 14 | # Optionally set the version of Python and requirements required to build your docs 15 | python: 16 | version: 3.7 17 | install: 18 | - requirements: docs/requirements-docs.txt 19 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | * Using welcoming and inclusive language 39 | * Being respectful of differing viewpoints and experiences 40 | * Gracefully accepting constructive criticism 41 | * Focusing on what is best for the community 42 | * Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | * The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | * Personal attacks, insulting/derogatory comments, or trolling 49 | * Public or private harassment 50 | * Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | * Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | * Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/) 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ -------------------------------------------------------------------------------- /CONTRIBUTING-ARCHIVED.md: -------------------------------------------------------------------------------- 1 | # ARCHIVED 2 | 3 | This project is `Archived` and is no longer actively maintained; 4 | We are not accepting contributions or Pull Requests. 5 | 6 | -------------------------------------------------------------------------------- /HomebrewFormula/cloud-guardrails.rb: -------------------------------------------------------------------------------- 1 | class CloudGuardrails < Formula 2 | include Language::Python::Virtualenv 3 | 4 | desc "Shiny new formula" 5 | homepage "https://github.com/salesforce/cloud-guardrails" 6 | url "https://files.pythonhosted.org/packages/f3/2b/43ea607db0a19fd2e2dd3684f42483d48cd98c706d6764901d46505a464d/cloud-guardrails-0.3.1.tar.gz" 7 | sha256 "a791155370343c992a9e9fb2497b396e0a85768dcc029954fb5ad9f507d26b80" 8 | 9 | depends_on "python3" 10 | 11 | resource "beautifulsoup4" do 12 | url "https://files.pythonhosted.org/packages/a1/69/daeee6d8f22c997e522cdbeb59641c4d31ab120aba0f2c799500f7456b7e/beautifulsoup4-4.10.0.tar.gz" 13 | sha256 "c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891" 14 | end 15 | 16 | resource "certifi" do 17 | url "https://files.pythonhosted.org/packages/6d/78/f8db8d57f520a54f0b8a438319c342c61c22759d8f9a1cd2e2180b5e5ea9/certifi-2021.5.30.tar.gz" 18 | sha256 "2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee" 19 | end 20 | 21 | resource "charset-normalizer" do 22 | url "https://files.pythonhosted.org/packages/68/32/95ddb68b9abeb89efd461852cdff5791d42fc5e4c528536f541091ffded3/charset-normalizer-2.0.5.tar.gz" 23 | sha256 "7098e7e862f6370a2a8d1a6398cd359815c45d12626267652c3f13dec58e2367" 24 | end 25 | 26 | resource "click" do 27 | url "https://files.pythonhosted.org/packages/21/83/308a74ca1104fe1e3197d31693a7a2db67c2d4e668f20f43a2fca491f9f7/click-8.0.1.tar.gz" 28 | sha256 "8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a" 29 | end 30 | 31 | resource "click-option-group" do 32 | url "https://files.pythonhosted.org/packages/3c/86/5de6d909d9dcc85627a178788ec3e8c3ef81cda175badb48ad0bb582628d/click-option-group-0.5.3.tar.gz" 33 | sha256 "a6e924f3c46b657feb5b72679f7e930f8e5b224b766ab35c91ae4019b4e0615e" 34 | end 35 | 36 | resource "colorama" do 37 | url "https://files.pythonhosted.org/packages/1f/bb/5d3246097ab77fa083a61bd8d3d527b7ae063c7d8e8671b1cf8c4ec10cbe/colorama-0.4.4.tar.gz" 38 | sha256 "5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b" 39 | end 40 | 41 | resource "idna" do 42 | url "https://files.pythonhosted.org/packages/cb/38/4c4d00ddfa48abe616d7e572e02a04273603db446975ab46bbcd36552005/idna-3.2.tar.gz" 43 | sha256 "467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" 44 | end 45 | 46 | resource "importlib-metadata" do 47 | url "https://files.pythonhosted.org/packages/f0/70/ca3dd67cdd368b957e73a8156f7e1a10339f9813e314cb8b4549526070da/importlib_metadata-4.8.1.tar.gz" 48 | sha256 "f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1" 49 | end 50 | 51 | resource "Jinja2" do 52 | url "https://files.pythonhosted.org/packages/39/11/8076571afd97303dfeb6e466f27187ca4970918d4b36d5326725514d3ed3/Jinja2-3.0.1.tar.gz" 53 | sha256 "703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" 54 | end 55 | 56 | resource "MarkupSafe" do 57 | url "https://files.pythonhosted.org/packages/bf/10/ff66fea6d1788c458663a84d88787bae15d45daa16f6b3ef33322a51fc7e/MarkupSafe-2.0.1.tar.gz" 58 | sha256 "594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a" 59 | end 60 | 61 | resource "PyYAML" do 62 | url "https://files.pythonhosted.org/packages/a0/a4/d63f2d7597e1a4b55aa3b4d6c5b029991d3b824b5bd331af8d4ab1ed687d/PyYAML-5.4.1.tar.gz" 63 | sha256 "607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e" 64 | end 65 | 66 | resource "requests" do 67 | url "https://files.pythonhosted.org/packages/e7/01/3569e0b535fb2e4a6c384bdbed00c55b9d78b5084e0fb7f4d0bf523d7670/requests-2.26.0.tar.gz" 68 | sha256 "b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" 69 | end 70 | 71 | resource "ruamel.yaml" do 72 | url "https://files.pythonhosted.org/packages/71/81/f597606e81f53eb69330e3f8287e9b5a3f7ed0481824036d550da705cd82/ruamel.yaml-0.17.16.tar.gz" 73 | sha256 "1a771fc92d3823682b7f0893ad56cb5a5c87c48e62b5399d6f42c8759a583b33" 74 | end 75 | 76 | resource "ruamel.yaml.clib" do 77 | url "https://files.pythonhosted.org/packages/8b/25/08e5ad2431a028d0723ca5540b3af6a32f58f25e83c6dda4d0fcef7288a3/ruamel.yaml.clib-0.2.6.tar.gz" 78 | sha256 "4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd" 79 | end 80 | 81 | resource "soupsieve" do 82 | url "https://files.pythonhosted.org/packages/c8/3f/e71d92e90771ac2d69986aa0e81cf0dfda6271e8483698f4847b861dd449/soupsieve-2.2.1.tar.gz" 83 | sha256 "052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc" 84 | end 85 | 86 | resource "tabulate" do 87 | url "https://files.pythonhosted.org/packages/ae/3d/9d7576d94007eaf3bb685acbaaec66ff4cdeb0b18f1bf1f17edbeebffb0a/tabulate-0.8.9.tar.gz" 88 | sha256 "eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7" 89 | end 90 | 91 | resource "typing-extensions" do 92 | url "https://files.pythonhosted.org/packages/ed/12/c5079a15cf5c01d7f4252b473b00f7e68ee711be605b9f001528f0298b98/typing_extensions-3.10.0.2.tar.gz" 93 | sha256 "49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e" 94 | end 95 | 96 | resource "urllib3" do 97 | url "https://files.pythonhosted.org/packages/4f/5a/597ef5911cb8919efe4d86206aa8b2658616d676a7088f0825ca08bd7cb8/urllib3-1.26.6.tar.gz" 98 | sha256 "f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" 99 | end 100 | 101 | resource "zipp" do 102 | url "https://files.pythonhosted.org/packages/3a/9f/1d4b62cbe8d222539a84089eeab603d8e45ee1f897803a0ae0860400d6e7/zipp-3.5.0.tar.gz" 103 | sha256 "f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" 104 | end 105 | 106 | def install 107 | virtualenv_create(libexec, "python3") 108 | virtualenv_install_with_resources 109 | end 110 | 111 | test do 112 | false 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Salesforce.com, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include cloud_guardrails/ *.txt *.yml *.json *.html *.csv *.j2 2 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | click = "==8.0.1" 8 | click-option-group = "==0.5.3" 9 | tabulate = "==0.8.9" 10 | "ruamel.yaml" = "==0.17.16" 11 | colorama = "==0.4.4" 12 | beautifulsoup4 = "==4.10.0" 13 | requests = "==2.26.0" 14 | PyYAML = "==5.4.1" 15 | Jinja2 = "==3.0.1" 16 | 17 | [dev-packages] 18 | 19 | [requires] 20 | python_version = "3.9" 21 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com) 4 | as soon as it is discovered. This library limits its runtime dependencies in 5 | order to reduce the total cost of ownership as much as can be, but all consumers 6 | should remain vigilant and have their security stakeholders review all third-party 7 | products (3PP) like this one and their dependencies. -------------------------------------------------------------------------------- /cloud_guardrails/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring 2 | import logging 3 | from logging import NullHandler 4 | 5 | # Set default handler when cloud_guardrails is used as library to avoid "No handler found" warnings. 6 | logging.getLogger(__name__).addHandler(NullHandler()) 7 | 8 | 9 | def set_stream_logger(name="cloud_guardrails", level=logging.DEBUG, format_string=None): 10 | """ 11 | Add a stream handler for the given name and level to the logging module. 12 | By default, this logs all cloud_guardrails messages to ``stdout``. 13 | >>> import cloud_guardrails 14 | >>> cloud_guardrails.set_stream_logger('cloud_guardrails.database.build', logging.INFO) 15 | :type name: string 16 | :param name: Log name 17 | :type level: int 18 | :param level: Logging level, e.g. ``logging.INFO`` 19 | :type format_string: str 20 | :param format_string: Log message format 21 | """ 22 | # remove existing handlers. since NullHandler is added by default 23 | handlers = logging.getLogger(name).handlers 24 | for handler in handlers: 25 | logging.getLogger(name).removeHandler(handler) 26 | if format_string is None: 27 | format_string = "%(asctime)s %(name)s [%(levelname)s] %(message)s" 28 | logger = logging.getLogger(name) 29 | logger.setLevel(level) 30 | handler = logging.StreamHandler() 31 | handler.setLevel(level) 32 | formatter = logging.Formatter(format_string) 33 | handler.setFormatter(formatter) 34 | logger.addHandler(handler) 35 | 36 | 37 | def set_log_level(verbose): 38 | """ 39 | Set Log Level based on click's count argument. 40 | 41 | Default log level to critical; otherwise, set to: warning for -v, info for -vv, debug for -vvv 42 | 43 | :param verbose: integer for verbosity count. 44 | :return: 45 | """ 46 | if verbose == 1: 47 | set_stream_logger(level=getattr(logging, "WARNING")) 48 | elif verbose == 2: 49 | set_stream_logger(level=getattr(logging, "INFO")) 50 | elif verbose >= 3: 51 | set_stream_logger(level=getattr(logging, "DEBUG")) 52 | else: 53 | set_stream_logger(level=getattr(logging, "CRITICAL")) 54 | 55 | 56 | def set_log_format_to_simple_warning(name: str, level : int = logging.WARNING): 57 | """A basic wrapper around set_stream_logger that sets the log formatter to a simple formatted warning""" 58 | format_string = "[%(levelname)s] %(message)s" 59 | set_stream_logger(name=name, level=level, format_string=format_string) 60 | -------------------------------------------------------------------------------- /cloud_guardrails/bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/cloud_guardrails/bin/__init__.py -------------------------------------------------------------------------------- /cloud_guardrails/bin/cli.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # Copyright (c) 2021, salesforce.com, inc. 3 | # All rights reserved. 4 | # Licensed under the BSD 3-Clause license. 5 | # For full license text, see the LICENSE file in the repo root 6 | # or https://opensource.org/licenses/BSD-3-Clause 7 | import click 8 | from cloud_guardrails import command 9 | from cloud_guardrails.bin.version import __version__ 10 | 11 | 12 | @click.group() 13 | @click.version_option(version=__version__) 14 | def cloud_guardrails(): 15 | """ 16 | Generates Azure Policies based on requirements and transforms them into Terraform. 17 | """ 18 | 19 | 20 | cloud_guardrails.add_command(command.create_parameters_file.create_parameters_file) 21 | cloud_guardrails.add_command(command.create_config_file.create_config_file) 22 | cloud_guardrails.add_command(command.describe_policy.describe_policy) 23 | cloud_guardrails.add_command(command.generate_terraform.generate_terraform) 24 | cloud_guardrails.add_command(command.list_policies.list_policies) 25 | cloud_guardrails.add_command(command.list_services.list_services) 26 | 27 | 28 | def main(): 29 | """ 30 | Generates Azure Policies based on requirements and transforms them into Terraform. 31 | """ 32 | cloud_guardrails() 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /cloud_guardrails/bin/version.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring 2 | __version__ = '0.3.2' -------------------------------------------------------------------------------- /cloud_guardrails/command/__init__.py: -------------------------------------------------------------------------------- 1 | from cloud_guardrails.command import create_parameters_file 2 | from cloud_guardrails.command import create_config_file 3 | from cloud_guardrails.command import describe_policy 4 | from cloud_guardrails.command import generate_terraform 5 | from cloud_guardrails.command import list_policies 6 | from cloud_guardrails.command import list_services 7 | -------------------------------------------------------------------------------- /cloud_guardrails/command/create_config_file.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | """Create a config file to specify which policies to select or exclude.""" 7 | import os 8 | import logging 9 | from pathlib import Path 10 | import click 11 | from cloud_guardrails import set_log_level 12 | from cloud_guardrails.shared.config import get_config_template 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | @click.command( 18 | name="create-config-file", 19 | short_help="Create a config file to specify which policies to select or exclude." 20 | ) 21 | @click.option("--output", "-o", "output_file", type=click.Path(exists=False), required=True, default="config.yml", help="The path to the output file",) 22 | @click.option("--verbose", "-v", "verbosity", count=True) 23 | def create_config_file(output_file: str, verbosity: int): 24 | """ 25 | Get Azure Policies 26 | """ 27 | 28 | set_log_level(verbosity) 29 | 30 | config_template = get_config_template() 31 | 32 | filename = Path(output_file).resolve() 33 | if os.path.exists(output_file): 34 | print("File exists. Removing...") 35 | os.remove(output_file) 36 | with open(filename, "a") as file_obj: 37 | for line in config_template: 38 | file_obj.write(line) 39 | print(f"Created config file: {filename}") 40 | -------------------------------------------------------------------------------- /cloud_guardrails/command/create_parameters_file.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | """ 7 | Create a file where we store the values of parameters. 8 | """ 9 | 10 | import os 11 | import logging 12 | from pathlib import Path 13 | import click 14 | from click_option_group import optgroup 15 | from cloud_guardrails import set_log_level 16 | # from cloud_guardrails.shared.parameters_categorized import get_parameters_template 17 | from cloud_guardrails.templates.parameters_template import ParameterTemplate 18 | from cloud_guardrails.shared import validate 19 | from cloud_guardrails.shared.config import get_default_config, get_config_from_file 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | @click.command( 25 | name="create-parameters-file", 26 | short_help="Create a YAML file containing Policy Parameters and default values." 27 | ) 28 | @click.option("--enforce", "-e", "enforce", type=str, is_flag=True, required=False, default=False, help="Where possible, set the effect for all policies to Deny.") 29 | @click.option("--output", "-o", "output_file", type=click.Path(exists=False), required=True, default="parameters.yml", help="The path to the output file") 30 | @click.option("--config", "-c", "config_file", type=click.Path(exists=False), required=False, help="The path to the config file") 31 | @click.option("--exclude-services", "exclude_services", type=str, help="Exclude specific services (comma-separated) without using a config file.", callback=validate.click_validate_comma_separated_excluded_services) 32 | # Parameter Options 33 | @optgroup.group("Parameter Options", help="",) 34 | @optgroup.option("--optional-only", "-oo", is_flag=True, default=False, help="Include policies containing OPTIONAL parameters") 35 | @optgroup.option("--required-only", "-ro", is_flag=True, default=False, help="Include policies containing REQUIRED parameters") 36 | @click.option("--verbose", "-v", "verbosity", count=True) 37 | def create_parameters_file( 38 | enforce: bool, 39 | output_file: str, 40 | config_file: str, 41 | optional_only: bool, 42 | required_only: bool, 43 | exclude_services: list, 44 | verbosity: int 45 | ): 46 | set_log_level(verbosity) 47 | if not config_file: 48 | logger.info( 49 | "You did not supply an config file. Consider creating one to exclude different policies. We will use the default one." 50 | ) 51 | config = get_default_config(exclude_services=exclude_services) 52 | else: 53 | config = get_config_from_file( 54 | config_file=config_file, exclude_services=exclude_services 55 | ) 56 | 57 | params_optional = True 58 | params_required = True 59 | # If optional_only is set, only leave params_optional to True 60 | if optional_only: 61 | params_required = False 62 | # If required_only is set, only leave params_required to True 63 | if required_only: 64 | params_optional = False 65 | 66 | # config_template = get_parameters_template() 67 | parameters_template = ParameterTemplate(config=config, params_optional=params_optional, 68 | params_required=params_required, enforce=enforce) 69 | parameters_template_rendered = parameters_template.rendered() 70 | 71 | filename = Path(output_file).resolve() 72 | if os.path.exists(output_file): 73 | print("File exists. Removing...") 74 | os.remove(output_file) 75 | with open(filename, "a") as file_obj: 76 | for line in parameters_template_rendered: 77 | file_obj.write(line) 78 | print(f"Created parameters file: {filename}") 79 | -------------------------------------------------------------------------------- /cloud_guardrails/command/describe_policy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | """ 7 | Supply a Policy's display name or a policy ID and get some metadata about the policy. 8 | """ 9 | import logging 10 | import json 11 | import ruamel.yaml 12 | import click 13 | from cloud_guardrails import set_log_level 14 | from click_option_group import optgroup, RequiredMutuallyExclusiveOptionGroup 15 | from cloud_guardrails.iam_definition.azure_policies import AzurePolicies 16 | from cloud_guardrails.shared.config import get_empty_config 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | @click.command( 21 | name="describe-policy", 22 | short_help="Get metadata about a Policy, given the policy name or ID." 23 | ) 24 | # Policy Lookup Options 25 | @optgroup.group("Policy Lookup Options", cls=RequiredMutuallyExclusiveOptionGroup, help="") 26 | @optgroup.option("--name", "-n", "display_name", type=str, help="The display name of the policy") 27 | @optgroup.option("--id", "-i", "policy_id", type=str, help="The short ID of the policy") 28 | # Other Options 29 | @optgroup.group("Other Options", help="") 30 | @optgroup.option("--format", "-f", "fmt", type=click.Choice(["json", "yaml"]), required=False, default="yaml", help="Output format") 31 | @click.option("--verbose", "-v", "verbosity", count=True) 32 | def describe_policy(display_name: str, policy_id: str, fmt: str, verbosity: bool): 33 | set_log_level(verbosity) 34 | azure_policies = AzurePolicies(config=get_empty_config()) 35 | # services = Services(config=get_empty_config()) 36 | if policy_id: 37 | policy_definition = azure_policies.get_policy_definition(policy_id=policy_id) 38 | else: 39 | policy_definition = azure_policies.get_policy_definition_by_display_name(display_name=display_name) 40 | results_json = policy_definition.json() 41 | results_json.pop("id", None) 42 | if fmt == "yaml": 43 | results_str = ruamel.yaml.dump(results_json, Dumper=ruamel.yaml.RoundTripDumper) 44 | print() 45 | print(results_str) 46 | else: 47 | print(json.dumps(results_json, indent=4)) 48 | -------------------------------------------------------------------------------- /cloud_guardrails/command/list_services.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | """ 7 | List services supported by Azure Built-in policies 8 | """ 9 | import logging 10 | import os 11 | import click 12 | from cloud_guardrails import set_log_level 13 | from cloud_guardrails.shared import utils 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | @click.command(name="list-services", short_help="List services supported by Azure Built-in policies") 19 | @click.option("-v", "--verbose", "verbosity", count=True) 20 | def list_services(verbosity): 21 | """List services supported by Azure Built-in policies""" 22 | set_log_level(verbosity) 23 | services = utils.get_service_names() 24 | for service in services: 25 | print(service) 26 | -------------------------------------------------------------------------------- /cloud_guardrails/iam_definition/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/cloud_guardrails/iam_definition/__init__.py -------------------------------------------------------------------------------- /cloud_guardrails/iam_definition/parameter.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import json 7 | 8 | 9 | class Parameter: 10 | """ 11 | Parameter properties 12 | 13 | https://docs.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure#parameter-properties 14 | """ 15 | 16 | def __init__(self, name: str, parameter_json: dict): 17 | self.name = name 18 | self.type = parameter_json.get("type") 19 | # Do some weird stuff because in this case, [] vs None has different implications 20 | if "defaultValue" in str(parameter_json): 21 | default_value = parameter_json.get("defaultValue", None) 22 | if default_value: 23 | self.default_value = default_value 24 | else: 25 | if self.type == "Array": 26 | self.default_value = [] 27 | else: 28 | self.default_value = None 29 | 30 | self.default_value = parameter_json.get("defaultValue", None) 31 | if not isinstance(self.default_value, type(None)): 32 | self.value = self.default_value 33 | else: 34 | self.value = None 35 | self.allowed_values = parameter_json.get("allowedValues", None) 36 | 37 | # Metadata 38 | self.metadata_json = parameter_json.get("metadata") 39 | self.description = self.metadata_json.get("description") 40 | self.display_name = self.metadata_json.get("displayName") 41 | self.schema = self.metadata_json.get("schema", None) 42 | self.category = self.metadata_json.get("category", None) 43 | self.strong_type = self.metadata_json.get("strongType", None) 44 | self.assign_permissions = self.metadata_json.get("assignPermissions", None) 45 | 46 | def __repr__(self): 47 | return json.dumps(self.json()) 48 | 49 | def json(self) -> dict: 50 | result = dict( 51 | name=self.name, 52 | type=self.type, 53 | description=self.description, 54 | display_name=self.display_name, 55 | ) 56 | # Return default value only if it has a value, or if it is an empty list or empty string 57 | if self.default_value or self.default_value == [] or self.default_value == "": 58 | result["default_value"] = self.default_value 59 | if not isinstance(self.value, type(None)): 60 | result["value"] = self.value 61 | if self.allowed_values: 62 | result["allowed_values"] = self.allowed_values 63 | if self.category: 64 | result["category"] = self.category 65 | if self.strong_type: 66 | result["strong_type"] = self.strong_type 67 | if self.assign_permissions: 68 | result["assign_permissions"] = self.assign_permissions 69 | return result 70 | 71 | @staticmethod 72 | def _allowed_values(parameter_json): 73 | allowed_values = parameter_json.get("allowedValues", None) 74 | allowed_values = [x.lower() for x in allowed_values] 75 | return allowed_values 76 | 77 | def parameter_config(self) -> dict: 78 | """The config format for this parameter to be fed into YAML""" 79 | result = {} 80 | # If there is no default value set, but it is a dict or a list, return an empty data type 81 | if self.default_value: 82 | default_value = self.default_value 83 | else: 84 | if self.type.lower() == "object": 85 | default_value = {} 86 | elif self.type.lower() == "array": 87 | default_value = [] 88 | elif self.type.lower() == "string": 89 | default_value = "" 90 | else: 91 | default_value = None 92 | result[self.name] = dict( 93 | type=self.type, 94 | default_value=default_value, 95 | allowed_values=self.allowed_values 96 | ) 97 | return result 98 | -------------------------------------------------------------------------------- /cloud_guardrails/iam_definition/properties.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import json 7 | from cloud_guardrails.iam_definition.parameter import Parameter 8 | 9 | 10 | class Properties: 11 | def __init__(self, properties_json: dict): 12 | self.properties_json = properties_json 13 | # Values 14 | display_name = properties_json.get("displayName") 15 | self.policy_type = properties_json.get("policyType") 16 | self.mode = properties_json.get("mode") 17 | self.description = properties_json.get("description") 18 | 19 | # Metadata 20 | self.metadata_json = properties_json.get("metadata") 21 | self.version = self.metadata_json.get("version", None) 22 | self.category = self.metadata_json.get("category", None) 23 | self.preview = self.metadata_json.get("preview", None) 24 | # if self.preview: 25 | # # # Sometimes, Preview is already in the display name. For example, "[ASC Private Preview] Deploy - Configure system-assigned managed identity to enable Azure Monitor assignments on VMs" 26 | # # if "Preview" not in display_name: 27 | # # self.display_name = f"[Preview]: {display_name}" 28 | # # # If the word 'Preview' is already in the display name, leave it 29 | # # else: 30 | # # self.display_name = display_name 31 | # else: 32 | if "[Preview]: " in display_name: 33 | display_name.replace("[Preview]: ", "") 34 | self.display_name = display_name 35 | self.deprecated = self.metadata_json.get("deprecated", None) 36 | 37 | # PolicyDefinition Rule and Parameters 38 | self.policy_rule = properties_json.get("policyRule") 39 | self.parameters = self._parameters(properties_json.get("parameters")) 40 | 41 | def __repr__(self): 42 | return json.dumps(self.json()) 43 | 44 | def json(self) -> dict: 45 | result = dict( 46 | policy_type=self.policy_type, 47 | mode=self.mode, 48 | description=self.description, 49 | version=self.version, 50 | preview=self.version, 51 | display_name=self.version, 52 | deprecated=self.version, 53 | policy_rule=self.version, 54 | ) 55 | if self.parameters: 56 | parameters_result = {} 57 | for parameter in self.parameters: 58 | parameters_result[parameter.name] = parameter.json() 59 | result["parameters"] = parameters_result 60 | return result 61 | 62 | def _parameters(self, parameters_json: dict) -> dict: 63 | parameters = {} 64 | if parameters_json: 65 | for name, value in parameters_json.items(): 66 | parameter = Parameter(name=name, parameter_json=value) 67 | parameters[name] = parameter 68 | return parameters 69 | 70 | @property 71 | def parameter_names(self) -> list: 72 | if self.parameters: 73 | return list(self.parameters.keys()) 74 | else: 75 | return [] 76 | 77 | @property 78 | def parameter_json(self) -> dict: 79 | result = {} 80 | if self.parameters: 81 | for name, value in self.parameters.items(): 82 | result[name] = value.json() 83 | return result 84 | else: 85 | return {} 86 | -------------------------------------------------------------------------------- /cloud_guardrails/scrapers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/cloud_guardrails/scrapers/__init__.py -------------------------------------------------------------------------------- /cloud_guardrails/scrapers/azure_docs.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import requests 7 | import os 8 | import logging 9 | from bs4 import BeautifulSoup 10 | 11 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 12 | 13 | 14 | def get_azure_html(link, file_path): 15 | file_name = os.path.basename(file_path) 16 | 17 | print(f"Getting the Azure documentation for: {file_name}") 18 | 19 | response = requests.get(link, allow_redirects=False) 20 | soup = BeautifulSoup(response.content, "html.parser") 21 | 22 | def cleanup_links(): 23 | # Replace the CSS stuff. Basically this: 24 | """ 25 | 26 | 27 | """ 28 | for link in soup.find_all("link"): 29 | if link.get("href").startswith("/"): 30 | temp = link.attrs["href"] 31 | link.attrs["href"] = link.attrs["href"].replace( 32 | temp, f"https://docs.microsoft.com{temp}" 33 | ) 34 | 35 | for script in soup.find_all("script"): 36 | try: 37 | if "src" in script.attrs: 38 | if script.get("src").startswith("/"): 39 | temp = script.attrs["src"] 40 | script.attrs["src"] = script.attrs["src"].replace( 41 | temp, f"https://docs.microsoft.com{temp}" 42 | ) 43 | except TypeError as t_e: 44 | logger.warning(t_e) 45 | logger.warning(script) 46 | except AttributeError as a_e: 47 | logger.warning(a_e) 48 | logger.warning(script) 49 | 50 | cleanup_links() 51 | 52 | if os.path.exists(file_path): 53 | print(f"Removing old file path: {file_path}") 54 | os.remove(file_path) 55 | with open(file_path, "w") as file: 56 | print(f"Creating new file: {os.path.abspath(file_path)}") 57 | file.write(str(soup.prettify())) 58 | file.close() 59 | logger.info("%s downloaded", file_name) 60 | return file_path 61 | -------------------------------------------------------------------------------- /cloud_guardrails/scrapers/compliance_data.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import json 7 | 8 | 9 | class BenchmarkEntry: 10 | def __init__( 11 | self, benchmark: str, category: str, requirement: str, requirement_id: str 12 | ): 13 | self.benchmark = benchmark 14 | self.category = category 15 | self.requirement = requirement 16 | self.requirement_id = requirement_id 17 | 18 | def __repr__(self): 19 | result = dict( 20 | benchmark=self.benchmark, 21 | category=self.category, 22 | requirement=self.requirement, 23 | requirement_id=self.requirement_id, 24 | ) 25 | return json.dumps(result) 26 | 27 | def __str__(self): 28 | return json.dumps(self.__repr__()) 29 | 30 | def json(self) -> dict: 31 | return json.loads(self.__repr__()) 32 | 33 | 34 | class PolicyDefinitionMetadata: 35 | def __init__( 36 | self, 37 | policy_id: str, 38 | service_name: str, 39 | effects: str, 40 | description: str, 41 | name: str, 42 | benchmark: str, 43 | category: str, 44 | requirement: str, 45 | requirement_id: str, 46 | github_link: str, 47 | github_version: str, 48 | ): 49 | self.policy_id = policy_id 50 | self.service_name = service_name 51 | self.effects = effects 52 | self.description = description 53 | self.name = name 54 | self.github_link = github_link 55 | self.github_version = github_version 56 | self.benchmarks = self._benchmarks( 57 | benchmark=benchmark, 58 | requirement=requirement, 59 | requirement_id=requirement_id, 60 | category=category, 61 | ) 62 | 63 | def __repr__(self) -> str: 64 | benchmark_response = {} 65 | for benchmark, benchmark_value in self.benchmarks.items(): 66 | benchmark_response[benchmark_value.benchmark] = benchmark_value.json() 67 | result = dict( 68 | policy_id=self.policy_id, 69 | effects=self.effects, 70 | description=self.description, 71 | name=self.name, 72 | service_name=self.service_name, 73 | github_link=self.github_link, 74 | github_version=self.github_version, 75 | benchmarks=benchmark_response, 76 | ) 77 | return json.dumps(result) 78 | 79 | def __str__(self): 80 | return self.__repr__() 81 | 82 | def json(self) -> dict: 83 | return json.loads(self.__repr__()) 84 | 85 | def _benchmarks( 86 | self, benchmark: str, category: str, requirement: str, requirement_id: str 87 | ) -> {BenchmarkEntry}: 88 | result = {} 89 | benchmark_entry = BenchmarkEntry( 90 | benchmark=benchmark, 91 | category=category, 92 | requirement=requirement, 93 | requirement_id=requirement_id, 94 | ) 95 | result[benchmark] = benchmark_entry 96 | return result 97 | 98 | def get_compliance_data_matching_policy_definition(self) -> dict: 99 | result = {} 100 | for benchmark, benchmark_value in self.benchmarks.items(): 101 | benchmark_name = benchmark_value.benchmark 102 | benchmark_string = ( 103 | f"{benchmark_value.benchmark}: {benchmark_value.requirement_id}" 104 | ) 105 | # benchmark_string = f"{benchmark_value.benchmark}: {benchmark_value.requirement_id} ({benchmark_value.requirement})" 106 | result[benchmark_name] = benchmark_string 107 | return result 108 | 109 | 110 | class ComplianceResultsTransformer: 111 | """ 112 | Transforms the metadata generated from scraping into the JSON format that is friendlier for our analysis. 113 | 114 | See the ComplianceData class to actually use this data. 115 | """ 116 | 117 | def __init__(self, results_list: list): 118 | self.results_list_json = results_list 119 | self.results = self._results() 120 | 121 | def __repr__(self) -> str: 122 | response = {} 123 | for result_key, result_value in self.results.items(): 124 | response[result_key] = result_value.json() 125 | return json.dumps(response) 126 | 127 | def json(self) -> dict: 128 | return json.loads(self.__repr__()) 129 | 130 | def __str__(self): 131 | return self.__repr__() 132 | 133 | def _results(self) -> {PolicyDefinitionMetadata}: 134 | results = {} 135 | for result in self.results_list_json: 136 | if result.get("name") not in results.keys(): 137 | policy_definition_metadata = PolicyDefinitionMetadata( 138 | policy_id=result.get("policy_id"), 139 | service_name=result.get("service_name"), 140 | effects=result.get("effects"), 141 | description=result.get("description"), 142 | name=result.get("name"), 143 | benchmark=result.get("benchmark"), 144 | category=result.get("category"), 145 | requirement=result.get("requirement"), 146 | requirement_id=result.get("requirement_id"), 147 | github_link=result.get("github_link"), 148 | github_version=result.get("github_version"), 149 | ) 150 | results[result.get("name")] = policy_definition_metadata 151 | else: 152 | benchmark = result.get("benchmark") 153 | category = result.get("category") 154 | requirement = result.get("requirement") 155 | requirement_id = result.get("requirement_id") 156 | benchmark_entry = BenchmarkEntry( 157 | benchmark=benchmark, 158 | category=category, 159 | requirement=requirement, 160 | requirement_id=requirement_id, 161 | ) 162 | results[result.get("name")].benchmarks[benchmark] = benchmark_entry 163 | return results 164 | 165 | -------------------------------------------------------------------------------- /cloud_guardrails/scrapers/parse_builtin_definitions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import os 7 | import json 8 | import copy 9 | from cloud_guardrails.shared import utils 10 | from cloud_guardrails.iam_definition.policy_definition import PolicyDefinition 11 | default_service_names = utils.get_service_names() 12 | default_service_names.sort() 13 | 14 | 15 | def get_service_policy_files(service_policy_directory: str) -> list: 16 | policy_files = [ 17 | f 18 | for f in os.listdir(service_policy_directory) 19 | if os.path.isfile(os.path.join(service_policy_directory, f)) 20 | ] 21 | policy_files.sort() 22 | return policy_files 23 | 24 | 25 | def create_azure_builtin_definition() -> dict: 26 | results = { 27 | "service_definitions": {}, 28 | "policy_definitions": {} 29 | } 30 | for service_name in default_service_names: 31 | # Get paths for all the policy files for that service 32 | service_policy_directory = os.path.join( 33 | utils.AZURE_POLICY_SERVICE_DIRECTORY, service_name 34 | ) 35 | policy_files = get_service_policy_files(service_policy_directory) 36 | # Add the service to the service definitions 37 | results["service_definitions"][service_name] = {} 38 | 39 | for policy_file_name in policy_files: 40 | policy_content = utils.read_json_file(str(os.path.join(service_policy_directory, policy_file_name))) 41 | policy_definition = PolicyDefinition( 42 | policy_content=policy_content, service_name=service_name, file_name=str(policy_file_name) 43 | ) 44 | # Look up by unique ID, like "051cba44-2429-45b9-9649-46cec11c7119" 45 | short_id = policy_definition.name 46 | 47 | # Add to service_definitions 48 | service_definition_entry = dict( 49 | display_name=policy_definition.display_name, 50 | short_id=short_id, 51 | service_name=policy_definition.service_name, 52 | description=policy_definition.properties.description, 53 | github_link=policy_definition.github_link, 54 | file_name=policy_file_name, 55 | allowed_effects=policy_definition.allowed_effects, 56 | no_params=policy_definition.no_params, 57 | params_optional=policy_definition.params_optional, 58 | params_required=policy_definition.params_required, 59 | is_deprecated=policy_definition.is_deprecated, 60 | audit_only=policy_definition.audit_only, 61 | modifies_resources=policy_definition.modifies_resources, 62 | parameter_names=policy_definition.parameter_names, 63 | ) 64 | results["service_definitions"][service_name][short_id] = service_definition_entry 65 | 66 | # Add to policy_definitions 67 | policy_definition_entry = copy.deepcopy(service_definition_entry) 68 | policy_definition_entry["policy_content"] = policy_content 69 | results["policy_definitions"][short_id] = policy_definition_entry 70 | return results 71 | -------------------------------------------------------------------------------- /cloud_guardrails/scrapers/standard.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | from bs4 import BeautifulSoup 7 | from cloud_guardrails.shared.utils import chomp_keep_single_spaces 8 | 9 | 10 | def get_requirement_id(input_text: str, replacement_string: str) -> str: 11 | """Pass in table.previous_sibling.previous_sibling.text and get the Azure Benchmark ID""" 12 | id_ownership_string = chomp_keep_single_spaces(input_text) 13 | this_id = id_ownership_string 14 | this_id = this_id.replace(f"ID : {replacement_string} ", "") 15 | this_id = this_id.replace(f"ID : {replacement_string}", "") 16 | this_id = this_id.replace(" Ownership : Customer", "") 17 | this_id = this_id.replace(" Ownership : Shared", "") 18 | return this_id 19 | 20 | 21 | def scrape_standard(html_file_path: str, benchmark_name: str, replacement_string: str): 22 | with open(html_file_path, "r") as f: 23 | soup = BeautifulSoup(f.read(), "html.parser") 24 | tables = soup.find_all("table") 25 | 26 | def get_service_name(github_link: str) -> str: 27 | """Pass in the github link and get the name of the service based on folder name""" 28 | elements = github_link.split("/") 29 | service_name = elements[-2:][0] 30 | service_name = service_name.replace("%20", " ") 31 | return service_name 32 | 33 | results = [] 34 | categories = [] 35 | for table in tables: 36 | table_identifier_sibling = table.previous_sibling.previous_sibling 37 | # Get requirement ID 38 | requirement_id = get_requirement_id( 39 | table_identifier_sibling.text, replacement_string 40 | ) 41 | 42 | if replacement_string in table_identifier_sibling.text: 43 | # Requirement Name 44 | requirement_sibling = ( 45 | table_identifier_sibling.previous_sibling.previous_sibling 46 | ) 47 | requirement = chomp_keep_single_spaces(requirement_sibling.text) 48 | 49 | # Benchmark Category 50 | category_sibling = requirement_sibling.previous_sibling.previous_sibling 51 | if "(Azure portal)" not in chomp_keep_single_spaces(category_sibling.text): 52 | category = chomp_keep_single_spaces(category_sibling.text) 53 | categories.append(category) 54 | else: 55 | category = categories[-1] 56 | 57 | rows = table.find_all("tr") 58 | if len(rows) == 0: 59 | continue 60 | for row in rows: 61 | cells = row.find_all("td") 62 | if len(cells) == 0 or len(cells) == 1: 63 | continue 64 | 65 | # Cell 0: Name with Azure Portal Link 66 | name_cell = cells[0] 67 | name_cell_href = cells[0].find_all("a", href=True) 68 | azure_portal_link = name_cell_href[0].attrs["href"] 69 | name_text = chomp_keep_single_spaces(name_cell.text) 70 | # Let's skip all the ones with Microsoft Managed Control; Azure be flexin', but that would unnecessarily 71 | # clutter our results since we are focusing on Customer/Shared responsibility only. 72 | if name_text.startswith("Microsoft Managed Control"): 73 | continue 74 | policy_id = azure_portal_link.partition("policyDefinitions%2F")[2] 75 | 76 | # Cell 1: Description 77 | description_cell = cells[1] 78 | description = chomp_keep_single_spaces(cells[1].text) 79 | 80 | # Cell 2: Effects 81 | effects_cell = cells[2] 82 | effects = chomp_keep_single_spaces(cells[2].text) 83 | 84 | # Cell 3: The Version and the GitHub link 85 | github_link_cell = cells[3] 86 | github_link_cell_href = cells[3].find_all("a", href=True) 87 | github_link = github_link_cell_href[0].attrs["href"] 88 | github_version = chomp_keep_single_spaces(github_link_cell_href[0].text) 89 | service_name = get_service_name(github_link) 90 | 91 | entry = dict( 92 | benchmark=benchmark_name, 93 | category=category, 94 | requirement=requirement, 95 | requirement_id=requirement_id, 96 | service_name=service_name, 97 | name=name_text, 98 | policy_id=policy_id, 99 | description=description, 100 | effects=effects, 101 | github_link=github_link, 102 | github_version=github_version, 103 | ) 104 | results.append(entry) 105 | else: 106 | raise Exception("No benchmark ID found. Figure out how to handle this.") 107 | return results 108 | -------------------------------------------------------------------------------- /cloud_guardrails/shared/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/cloud_guardrails/shared/__init__.py -------------------------------------------------------------------------------- /cloud_guardrails/shared/data/compliance-data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/cloud_guardrails/shared/data/compliance-data.xlsx -------------------------------------------------------------------------------- /cloud_guardrails/shared/validate.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import logging 7 | import click 8 | from cloud_guardrails.shared import utils 9 | 10 | 11 | def click_validate_supported_azure_service(ctx, param, value): 12 | supported_services = utils.get_service_names() 13 | supported_services.append("all") 14 | 15 | if value in supported_services: 16 | return value 17 | else: 18 | raise click.BadParameter( 19 | f"Supply a supported Azure service. Supported services are: {', '.join(supported_services)}" 20 | ) 21 | 22 | 23 | def click_validate_comma_separated_excluded_services(ctx, param, value): 24 | supported_services = utils.get_service_names() 25 | if value is not None: 26 | try: 27 | if value == "": 28 | return [] 29 | else: 30 | excluded_services = value.split(",") 31 | for service in excluded_services: 32 | if service not in supported_services: 33 | raise click.BadParameter( 34 | f"The service name {service} is invalid. Please provide a comma " 35 | f"separated list of supported services from the list: " 36 | f"{','.join(supported_services)}" 37 | ) 38 | return excluded_services 39 | except ValueError: 40 | raise click.BadParameter( 41 | "Supply the list of resource names to exclude from results in a comma separated string." 42 | ) 43 | -------------------------------------------------------------------------------- /cloud_guardrails/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/cloud_guardrails/templates/__init__.py -------------------------------------------------------------------------------- /cloud_guardrails/templates/config-template.yml.j2: -------------------------------------------------------------------------------- 1 | #### 2 | # match_only_keywords: Use this to only apply policies that match any of these keywords 3 | # exclude_keywords: Use this to skip policies that have any of these keywords in the display name 4 | # exclude_services: Specify services that you want to exclude entirely. 5 | # exclude_policies: Specify Azure Policy Definition displayNames that you want to exclude from the results, sorted by service 6 | #### 7 | 8 | # Use this to only apply policies that match any of these keywords 9 | # Example: "encrypt", "SQL", "HTTP" 10 | match_only_keywords: 11 | - "" 12 | {% for keyword in t.match_only_keywords %} 13 | - "{{ keyword }}" 14 | {% endfor %} 15 | 16 | exclude_keywords: 17 | - "" 18 | - "virtual network service endpoint" 19 | #- "private link" 20 | {% for keyword in t.exclude_keywords %} 21 | - "{{ keyword }}" 22 | {% endfor %} 23 | 24 | # Specify services that you want to exclude entirely. 25 | # Uncomment the services mentioned below if you want to exclude them. 26 | exclude_services: 27 | - "" 28 | - "Guest Configuration" 29 | {% set existing_exclude_services = ["Guest Configuration"] %} 30 | {% for service in t.service_names %}{% if service not in existing_exclude_services %} #- "{{ service }}" 31 | {% endif %}{% endfor %} 32 | 33 | # Specify Azure Policy Definition displayNames that you want to exclude from the results 34 | exclude_policies: 35 | General: 36 | - "Allow resource creation only in Asia data centers" 37 | - "Allow resource creation only in European data centers" 38 | - "Allow resource creation only in India data centers" 39 | - "Allow resource creation only in United States data centers" 40 | Tags: 41 | - "Allow resource creation if 'department' tag set" 42 | - "Allow resource creation if 'environment' tag value in allowed values" 43 | API Management: 44 | # This collides with the same one from App Platform 45 | - "API Management services should use a virtual network" 46 | App Platform: 47 | # This collides with the same one from API Management 48 | - "Azure Spring Cloud should use network injection" 49 | Guest Configuration: 50 | # This outputs a parameter called "Cert:" that breaks the parameter yaml format 51 | - "Audit Windows machines that contain certificates expiring within the specified number of days" 52 | Network: 53 | # This one is overly cumbersome for most organizations 54 | - "Network interfaces should not have public IPs" 55 | 56 | {% set existing_exclusions = ["General", "Tags", "API Management", "App Platform", "Guest Configuration"] %} 57 | {% for service in t.service_names %}{% if service not in existing_exclusions %} 58 | {{ service }}: 59 | - "" 60 | {% endif %}{% endfor %} 61 | -------------------------------------------------------------------------------- /cloud_guardrails/templates/config_template.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import os 7 | from cloud_guardrails.shared import utils 8 | from jinja2 import Template, Environment, FileSystemLoader 9 | 10 | DEFAULT_CONFIG_FILE = os.path.abspath( 11 | os.path.join(os.path.dirname(__file__), "config-template.yml.j2") 12 | ) 13 | 14 | 15 | def get_config_template() -> str: 16 | template_contents = dict( 17 | match_only_keywords=[], 18 | exclude_keywords=[], 19 | service_names=utils.get_service_names(), 20 | ) 21 | template_path = os.path.join(os.path.dirname(__file__)) 22 | env = Environment(loader=FileSystemLoader(template_path)) # nosec 23 | template = env.get_template("config-template.yml.j2") 24 | return template.render(t=template_contents) 25 | -------------------------------------------------------------------------------- /cloud_guardrails/templates/parameters-template.yml.j2: -------------------------------------------------------------------------------- 1 | {# service: Batch #} 2 | {%- for service, policy_name in t.categorized_parameters.items() -%} 3 | # --------------------------------------------------------------------------------------------------------------------- 4 | # {{ service }} 5 | # --------------------------------------------------------------------------------------------------------------------- 6 | {{ service }}{{ ':' }} 7 | {#- policy_name: 'Metric alert rules should be configured on Batch accounts' -#} 8 | {%- for this_policy_name, parameter_segments in policy_name.items() -%} 9 | {#- for parameter_segment in [effect, denyListOfStuff] -#} 10 | {{ "\n \"" }}{{ this_policy_name }}{{ '":\n' }} 11 | {%- for parameter_segment in parameter_segments -%} 12 | {%- filter indent(width=2) -%} 13 | {#- List the name of the parameter -#} 14 | {{ " " }} {{ parameter_segment.name }}: 15 | {#- If the value is not null -#} 16 | {%- if parameter_segment.value is not is_none_instance -%} 17 | {#- If the value is a list -#} 18 | {%- if parameter_segment.value is is_a_list -%} 19 | {#- If the list has values in it -#} 20 | {%- if parameter_segment.value %} 21 | {#- If it's [], set it to [] -#} 22 | {{ '\n - ' }}{{ parameter_segment.value|join('\n - ') }} 23 | {%- else -%} 24 | {{ ' []' }} 25 | {%- endif -%} 26 | {%- else -%} 27 | {{ ' ' }}{{ parameter_segment.value }}{{ ' ' }} 28 | {%- endif -%} 29 | {%- endif -%} 30 | {#- When there are no default parameters, insert a comment -#} 31 | {%- if parameter_segment.default_value is is_none_instance -%} 32 | {{ " # Note: No default parameters" }} 33 | {%- endif -%} 34 | {#- Example: AuditIfNotExists, Disabled -#} 35 | {%- if parameter_segment.allowed_values is not is_none_instance -%} 36 | {{ ''-}}{{ '' }} # Allowed: ["{{ parameter_segment.allowed_values|join("\", \"") }}"] 37 | {%- endif -%}{% endfilter %}{{ "\n" }} 38 | {%- endfor -%} 39 | {%- endfor -%} 40 | {%- endfor -%} 41 | -------------------------------------------------------------------------------- /cloud_guardrails/templates/parameters_template.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import os 7 | import json 8 | from jinja2 import Environment, FileSystemLoader 9 | from cloud_guardrails.iam_definition.azure_policies import AzurePolicies 10 | from cloud_guardrails.shared.parameters_categorized import CategorizedParameters 11 | from cloud_guardrails.shared.config import DEFAULT_CONFIG, Config 12 | from cloud_guardrails.shared import utils 13 | 14 | 15 | class ParameterSegment: 16 | def __init__(self, parameter_name: str, parameter_type: str, value=None, default_value=None, 17 | allowed_values: list = None): 18 | self.name = parameter_name 19 | self.type = parameter_type 20 | self.allowed_values = allowed_values 21 | self.default_value = default_value 22 | self.value = value 23 | 24 | def json(self): 25 | return dict( 26 | name=self.name, 27 | type=self.type, 28 | allowed_values=self.allowed_values, 29 | default_value=self.default_value, 30 | value=self.value 31 | ) 32 | 33 | def __repr__(self) -> str: 34 | return json.dumps(self.__dict__) 35 | 36 | 37 | class ParameterTemplate: 38 | def __init__( 39 | self, 40 | config: Config = DEFAULT_CONFIG, 41 | params_optional: bool = False, 42 | params_required: bool = False, 43 | enforce: bool = False 44 | ): 45 | self.azure_policies = AzurePolicies(service_names=["all"], config=config) 46 | self.enforce = enforce 47 | categorized_parameters = CategorizedParameters( 48 | azure_policies=self.azure_policies, 49 | params_optional=params_optional, 50 | params_required=params_required, 51 | audit_only=False, 52 | enforce=enforce 53 | ) 54 | self.parameters_config = self.set_parameter_config(categorized_parameters=categorized_parameters) 55 | 56 | def json(self): 57 | results = {} 58 | for service_name, service_policies in self.parameters_config.items(): 59 | results[service_name] = {} 60 | for policy_name, policy_parameters in service_policies.items(): 61 | results[service_name][policy_name] = [] 62 | for parameter_segment in policy_parameters: 63 | results[service_name][policy_name].append(parameter_segment.json()) 64 | 65 | return results 66 | 67 | def __repr__(self): 68 | return json.dumps(self.json()) 69 | 70 | def get_parameter_value(self, parameter_name: str, default_value, allowed_values): 71 | """If the parameter value is a non-default, then we will return it here. Or if the user supplies --enforce, we will change 'Audit' to 'Deny'""" 72 | # Azure Policy effects: https://docs.microsoft.com/en-us/azure/governance/policy/concepts/effects 73 | result = default_value 74 | # Let's handle the "effect" parameter 75 | if parameter_name == "effect" or parameter_name == "Effect": # This is faster than using .lower() 76 | if self.enforce: 77 | # It could be Capitalized or lowercase in allowed_values 78 | if "Deny" in allowed_values: 79 | result = "Deny" 80 | # lowercase = [x.lower() for x in allowed_values] 81 | if "deny" in allowed_values: 82 | result = "deny" 83 | return result 84 | 85 | def set_parameter_config(self, categorized_parameters: CategorizedParameters) -> dict: 86 | results = {} 87 | for service_name, service_policies in categorized_parameters.service_categorized_parameters.items(): 88 | results[service_name] = {} 89 | for policy_name, policy_parameters in service_policies.items(): 90 | results[service_name][policy_name] = [] 91 | policy_id = self.azure_policies.get_policy_id_by_display_name(policy_name) 92 | for parameter_name, parameter_details in policy_parameters.items(): 93 | try: 94 | allowed_values = self.azure_policies.get_allowed_values_for_parameter(policy_id=policy_id, parameter_name=parameter_name) 95 | default_value = self.azure_policies.get_default_value_for_parameter(policy_id=policy_id, parameter_name=parameter_name) 96 | parameter_type = self.azure_policies.get_parameter_type(policy_id=policy_id, parameter_name=parameter_name) 97 | value = self.get_parameter_value(parameter_name=parameter_name, default_value=default_value, allowed_values=allowed_values) 98 | parameter_segment = ParameterSegment(parameter_name=parameter_name, parameter_type=parameter_type, default_value=default_value, value=value, allowed_values=allowed_values) 99 | results[service_name][policy_name].append(parameter_segment) 100 | except AttributeError: 101 | # This occurs sometimes because the IAM definition has some parameters that are not legit, like "policy_id" 102 | pass 103 | return results 104 | 105 | def rendered(self) -> str: 106 | template_contents = dict( 107 | categorized_parameters=self.parameters_config 108 | ) 109 | template_path = os.path.join(os.path.dirname(__file__)) 110 | env = Environment(loader=FileSystemLoader(template_path), lstrip_blocks=True) # nosec 111 | env.tests['is_none_instance'] = utils.is_none_instance 112 | 113 | def is_list(value): 114 | return isinstance(value, list) 115 | 116 | env.tests['is_a_list'] = is_list 117 | 118 | template = env.get_template("parameters-template.yml.j2") 119 | result = template.render(t=template_contents) 120 | return result 121 | -------------------------------------------------------------------------------- /cloud_guardrails/terraform/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/cloud_guardrails/terraform/__init__.py -------------------------------------------------------------------------------- /cloud_guardrails/terraform/no-parameters/policy-initiative-no-params.tf.j2: -------------------------------------------------------------------------------- 1 | locals { 2 | name_{{ t.label }} = "{{ t.initiative_name }}" 3 | subscription_name_{{ t.label }} = "{{ t.subscription_name }}" 4 | management_group_{{ t.label }} = "{{ t.management_group }}" 5 | enforcement_mode_{{ t.label }} = {{ t.enforcement_mode }} 6 | policy_ids_{{ t.label }} = [{% for service_name, service_policies in t.policy_id_pairs.items() %} 7 | # ----------------------------------------------------------------------------------------------------------------- 8 | # {{ service_name }} 9 | # -----------------------------------------------------------------------------------------------------------------{% for policy_id, policy_details in service_policies.items() %} 10 | "{{ policy_details.short_id }}", # {{ policy_details.display_name }} {% endfor %} 11 | {% endfor %} 12 | ] 13 | } 14 | 15 | # --------------------------------------------------------------------------------------------------------------------- 16 | # Azure Policy name lookups: 17 | # Because the policies are built-in, we can just look up their IDs by their names. 18 | # --------------------------------------------------------------------------------------------------------------------- 19 | data "azurerm_policy_definition" "{{ t.label }}" { 20 | count = length(local.policy_ids_{{ t.label }}) 21 | name = element(local.policy_ids_{{ t.label }}, count.index) 22 | } 23 | 24 | locals { 25 | {{ t.label }}_policy_definitions = flatten([tolist([ 26 | for definition in data.azurerm_policy_definition.{{ t.label }}.*.id : 27 | map("policyDefinitionId", definition) 28 | ]) 29 | ]) 30 | } 31 | 32 | # --------------------------------------------------------------------------------------------------------------------- 33 | # Conditional data lookups: If the user supplies management group, look up the ID of the management group 34 | # --------------------------------------------------------------------------------------------------------------------- 35 | data "azurerm_management_group" "{{ t.label }}" { 36 | count = local.management_group_{{ t.label }} != "" ? 1 : 0 37 | display_name = local.management_group_{{ t.label }} 38 | } 39 | 40 | ### If the user supplies subscription, look up the ID of the subscription 41 | data "azurerm_subscriptions" "{{ t.label }}" { 42 | count = local.subscription_name_{{ t.label }} != "" ? 1 : 0 43 | display_name_contains = local.subscription_name_{{ t.label }} 44 | } 45 | 46 | locals { 47 | {{ t.label }}_scope = local.management_group_{{ t.label }} != "" ? data.azurerm_management_group.{{ t.label }}[0].id : element(data.azurerm_subscriptions.{{ t.label }}[0].subscriptions.*.id, 0) 48 | } 49 | 50 | # --------------------------------------------------------------------------------------------------------------------- 51 | # Policy Initiative 52 | # --------------------------------------------------------------------------------------------------------------------- 53 | resource "azurerm_policy_set_definition" "{{ t.label }}" { 54 | name = local.name_{{ t.label }} 55 | policy_type = "Custom" 56 | display_name = local.name_{{ t.label }} 57 | description = local.name_{{ t.label }} 58 | management_group_name = local.management_group_{{ t.label }} == "" ? null : local.management_group_{{ t.label }} 59 | policy_definitions = tostring(jsonencode(local.{{ t.label }}_policy_definitions)) 60 | metadata = tostring(jsonencode({ 61 | category = local.name_{{ t.label }} 62 | })) 63 | } 64 | 65 | # --------------------------------------------------------------------------------------------------------------------- 66 | # Azure Policy Assignments 67 | # Apply the Policy Initiative to the specified scope 68 | # --------------------------------------------------------------------------------------------------------------------- 69 | resource "azurerm_policy_assignment" "{{ t.label }}" { 70 | name = local.name_{{ t.label }} 71 | policy_definition_id = azurerm_policy_set_definition.{{ t.label }}.id 72 | scope = local.{{ t.label }}_scope 73 | enforcement_mode = local.enforcement_mode_{{ t.label }} 74 | } 75 | 76 | # --------------------------------------------------------------------------------------------------------------------- 77 | # Outputs 78 | # --------------------------------------------------------------------------------------------------------------------- 79 | output "{{ t.label }}_policy_assignment_ids" { 80 | value = azurerm_policy_assignment.{{ t.label }}.id 81 | description = "The IDs of the Policy Assignments." 82 | } 83 | 84 | output "{{ t.label }}_scope" { 85 | value = local.{{ t.label }}_scope 86 | description = "The target scope - either the management group or subscription, depending on which parameters were supplied" 87 | } 88 | 89 | output "{{ t.label }}_policy_set_definition_id" { 90 | value = azurerm_policy_set_definition.{{ t.label }}.id 91 | description = "The ID of the Policy Set Definition." 92 | } 93 | 94 | output "{{ t.label }}_count_of_policies_applied" { 95 | description = "The number of Policies applied as part of the Policy Initiative" 96 | value = length(local.policy_ids_{{ t.label }}) 97 | } -------------------------------------------------------------------------------- /cloud_guardrails/terraform/provider/provider.tf.j2: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | version = "{{ t.provider_version }}" 3 | features {} 4 | } 5 | -------------------------------------------------------------------------------- /cloud_guardrails/terraform/with-parameters/policy-initiative-with-parameters.tf.j2: -------------------------------------------------------------------------------- 1 | locals { 2 | name_{{ t.name }} = "{{ t.name }}" 3 | subscription_name_{{ t.name }} = "{{ t.subscription_name }}" 4 | management_group_{{ t.name }} = "{{ t.management_group }}" 5 | category_{{ t.name }} = "{{ t.category }}" 6 | enforcement_mode_{{ t.name }} = {{ t.enforcement_mode }} 7 | policy_ids_{{ t.name }} = [{% for service_name, service_policies in t.policy_id_pairs.items() %} 8 | # ----------------------------------------------------------------------------------------------------------------- 9 | # {{ service_name }} 10 | # -----------------------------------------------------------------------------------------------------------------{% for policy_id, policy_details in service_policies.items() %} 11 | "{{ policy_details.short_id }}", # {{ policy_details.display_name }} {% endfor %} 12 | {% endfor %} 13 | ] 14 | policy_definition_map = { 15 | {% for service_name, service_policies in t.policy_id_pairs.items() -%} 16 | {% for policy_id, policy_details in service_policies.items() -%} 17 | "{{ policy_details.display_name }}" = "{{ policy_details.long_id }}", 18 | {% endfor %} 19 | {%- endfor -%} 20 | } 21 | } 22 | 23 | # --------------------------------------------------------------------------------------------------------------------- 24 | # Conditional data lookups: If the user supplies management group, look up the ID of the management group 25 | # --------------------------------------------------------------------------------------------------------------------- 26 | data "azurerm_management_group" "{{ t.name }}" { 27 | count = local.management_group_{{ t.name }} != "" ? 1 : 0 28 | display_name = local.management_group_{{ t.name }} 29 | } 30 | 31 | ### If the user supplies subscription, look up the ID of the subscription 32 | data "azurerm_subscriptions" "{{ t.name }}" { 33 | count = local.subscription_name_{{ t.name }} != "" ? 1 : 0 34 | display_name_contains = local.subscription_name_{{ t.name }} 35 | } 36 | 37 | locals { 38 | scope = local.management_group_{{ t.name }} != "" ? data.azurerm_management_group.{{ t.name }}[0].id : element(data.azurerm_subscriptions.{{ t.name }}[0].subscriptions.*.id, 0) 39 | } 40 | 41 | # --------------------------------------------------------------------------------------------------------------------- 42 | # Azure Policy Definition Lookups 43 | # --------------------------------------------------------------------------------------------------------------------- 44 | 45 | data "azurerm_policy_definition" "{{ t.name }}_definition_lookups" { 46 | count = length(local.policy_ids_{{ t.name }}) 47 | name = local.policy_ids_{{ t.name }}[count.index] 48 | } 49 | 50 | # --------------------------------------------------------------------------------------------------------------------- 51 | # Azure Policy Initiative Definition 52 | # --------------------------------------------------------------------------------------------------------------------- 53 | 54 | resource "azurerm_policy_set_definition" "{{ t.name }}" { 55 | name = local.name_{{ t.name }} 56 | policy_type = "Custom" 57 | display_name = local.name_{{ t.name }} 58 | description = local.name_{{ t.name }} 59 | management_group_name = local.management_group_{{ t.name }} == "" ? null : local.management_group_{{ t.name }} 60 | metadata = tostring(jsonencode({ 61 | category = local.category_{{ t.name }} 62 | })) 63 | {%- for service_name, service_policy_details in t.policy_definition_reference_parameters.items() -%} 64 | {% for policy_definition_name, policy_definition_details in service_policy_details.items() %} 65 | policy_definition_reference { 66 | policy_definition_id = lookup(local.policy_definition_map, "{{ policy_definition_name|normalize_display_name_string }}") 67 | parameter_values = jsonencode({ 68 | {%- for parameter, parameter_details in policy_definition_details.items() %} 69 | {%- if parameter_details['parameter_value'] is string -%} 70 | {{ "\n " }}{{ parameter_details["parameter_name"] }} = { "value" : {{ parameter_details['parameter_value']|format_parameter_value }} } 71 | {%- else -%} 72 | {%- if 'value' not in parameter_details['parameter_value'].keys() -%} 73 | {{ "\n " }}{{ parameter_details["parameter_name"] }} = { "value" : {{ parameter_details['parameter_value']['type']|get_placeholder_value_given_type }} } 74 | {%- else -%} 75 | {{ "\n " }}{{ parameter_details["parameter_name"] }} = { "value" : {{ parameter_details['parameter_value']['value']|format_parameter_value }} } 76 | {%- endif -%} 77 | {%- endif -%} 78 | {% endfor %} 79 | }) 80 | reference_id = "{{ policy_definition_name|strip_special_characters }}" 81 | } 82 | {% endfor %}{%- endfor -%} 83 | } 84 | 85 | # --------------------------------------------------------------------------------------------------------------------- 86 | # Azure Policy Assignments 87 | # Apply the Policy Initiative to the specified scope 88 | # --------------------------------------------------------------------------------------------------------------------- 89 | resource "azurerm_policy_assignment" "{{ t.name }}" { 90 | name = local.name_{{ t.name }} 91 | policy_definition_id = azurerm_policy_set_definition.{{ t.name }}.id 92 | scope = local.scope 93 | enforcement_mode = local.enforcement_mode_{{ t.name }} 94 | } 95 | 96 | 97 | # --------------------------------------------------------------------------------------------------------------------- 98 | # Outputs 99 | # --------------------------------------------------------------------------------------------------------------------- 100 | output "{{ t.name }}_policy_assignment_ids" { 101 | value = azurerm_policy_assignment.{{ t.name }}.id 102 | description = "The IDs of the Policy Assignments." 103 | } 104 | 105 | output "{{ t.name }}_scope" { 106 | value = local.scope 107 | description = "The target scope - either the management group or subscription, depending on which parameters were supplied" 108 | } 109 | 110 | output "{{ t.name }}_policy_set_definition_id" { 111 | value = azurerm_policy_set_definition.{{ t.name }}.id 112 | description = "The ID of the Policy Set Definition." 113 | } 114 | -------------------------------------------------------------------------------- /docs/cheatsheet.md: -------------------------------------------------------------------------------- 1 | # Cheatsheet 2 | 3 | ### Quick start 4 | 5 | ```bash 6 | # Install Terraform (prerequisite) 7 | brew install tfenv 8 | tfenv install 0.12.31 9 | 10 | # Install via Homebrew 11 | brew tap salesforce/cloud-guardrails https://github.com/salesforce/cloud-guardrails 12 | brew install cloud-guardrails 13 | 14 | # Generate files for Guardrails that do not require parameters 15 | cloud-guardrails generate-terraform --no-params --subscription example 16 | 17 | # Log into Azure and set your subscription 18 | az login 19 | az account set --subscription example 20 | 21 | # Apply the policies 22 | terraform init 23 | terraform plan 24 | terraform apply -auto-approve 25 | ``` 26 | 27 | ### Writing Policies 28 | 29 | ```bash 30 | # No Parameters 31 | cloud-guardrails generate-terraform --no-params --subscription example 32 | 33 | # Optional Parameters (i.e., all the policies have default parameter values) 34 | cloud-guardrails generate-terraform --params-optional --subscription example 35 | 36 | # Required Parameters 37 | cloud-guardrails generate-terraform --params-required \ 38 | --service Kubernetes \ 39 | --subscription example 40 | 41 | # Create Config file 42 | cloud-guardrails create-config-file --output config.yml 43 | 44 | # Create Parameters file 45 | cloud-guardrails create-parameters-file --output parameters.yml 46 | ``` 47 | 48 | ### Querying Policy Data 49 | 50 | ``` 51 | # list-services: List all the services supported by Azure built-in Policies 52 | cloud-guardrails list-services 53 | 54 | # list-policies: List all the existing built-in Azure Policies 55 | cloud-guardrails list-policies --service "Kubernetes" --all-policies 56 | cloud-guardrails list-policies --service "Kubernetes" --no-params 57 | cloud-guardrails list-policies --service "Kubernetes" --audit-only 58 | 59 | # describe-policy: Describe a specific policy based on display name or the short policy ID 60 | cloud-guardrails describe-policy --id 7c1b1214-f927-48bf-8882-84f0af6588b1 61 | cloud-guardrails describe-policy --name "Storage accounts should use customer-managed key for encryption" 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/contributing/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Setup 4 | 5 | * Set up the virtual environment 6 | 7 | ```bash 8 | # Set up the virtual environment 9 | python3 -m venv ./venv && source venv/bin/activate 10 | pip3 install -r requirements.txt 11 | ``` 12 | 13 | * Build the package 14 | 15 | ```bash 16 | # To build only 17 | make build 18 | 19 | # To build and install 20 | make install 21 | 22 | # To run tests 23 | make test 24 | 25 | # To clean local dev environment 26 | make clean 27 | ``` 28 | 29 | ## Other tasks 30 | 31 | * Update with the latest Azure Compliance data 32 | 33 | ```bash 34 | make update-data 35 | ``` 36 | 37 | * Update the policy summary tables 38 | 39 | ```bash 40 | make update-policy-table 41 | ``` 42 | 43 | * Update the Azure Policy Git submodule and merge it 44 | 45 | ```bash 46 | # Without merge 47 | make update-submodule 48 | 49 | # With merge 50 | make update-submodule-with-merge 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/custom.css: -------------------------------------------------------------------------------- 1 | div.doc-contents:not(.first) { 2 | padding-left: 25px; 3 | border-left: 4px solid rgba(230, 230, 230); 4 | margin-bottom: 80px; 5 | } 6 | 7 | div.doc-contents:not(.first) { 8 | padding-left: 25px; 9 | border-left: 4px solid rgba(230, 230, 230); 10 | margin-bottom: 80px; 11 | } 12 | 13 | div.doc-module { 14 | font-size: 1em; 15 | } 16 | 17 | h5.doc-heading { 18 | text-transform: none !important; 19 | } 20 | 21 | h6.hidden-toc { 22 | margin: 0 !important; 23 | position: relative; 24 | top: -70px; 25 | } 26 | 27 | h6.hidden-toc::before { 28 | margin-top: 0 !important; 29 | padding-top: 0 !important; 30 | } 31 | 32 | h6.hidden-toc a.headerlink { 33 | display: none; 34 | } 35 | 36 | td code { 37 | word-break: normal !important; 38 | } 39 | 40 | td p { 41 | margin-top: 0 !important; 42 | margin-bottom: 0 !important; 43 | } 44 | -------------------------------------------------------------------------------- /docs/images/demo-480.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/docs/images/demo-480.gif -------------------------------------------------------------------------------- /docs/images/demo-480.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/docs/images/demo-480.mov -------------------------------------------------------------------------------- /docs/images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/docs/images/demo.gif -------------------------------------------------------------------------------- /docs/images/demo.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/docs/images/demo.mov -------------------------------------------------------------------------------- /docs/images/example-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce/cloud-guardrails/9c4eb5c49faec44b10119422bee54ec064391c7c/docs/images/example-output.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Cloud Guardrails 2 | 3 | Cloud Guardrails allows you to rapidly cherry-pick cloud security guardrails by generating Terraform files that create Azure Policy Initiatives. 4 | 5 | # Overview 6 | 7 | [Azure Policies](https://docs.microsoft.com/en-us/azure/governance/policy/overview) - similar to [AWS Service Control Policies (SCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) - allows Azure customers to enforce organizational standards and enforce security policies at scale. You can use Azure Policies to evaluate the overall state of your environment, and drill down to the security status per resource and per policy. **For example, you can prevent users from creating any unencrypted resources or security group rules that allow SSH/RDP Access to 0.0.0.0/0**. 8 | 9 | Azure Provides **400+ built-in security policies**. This presents an incredible opportunity for customers who want to enforce preventative security guardrails from the start. However, deciding which of the 400+ built-in policies you want to enforce, and which stages you want to roll them out in can be a bit intimidating at the start. 10 | 11 | To help maximize coverage and ease the rollout process, I created this tool so that you can: 12 | 13 | * **Cherry-pick and bulk-select the security policies you want** according to your specific criteria 14 | * Enforce low-friction policies within **minutes** 15 | * Easily roll back policies that you don't want 16 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | #### Option 1: Homebrew 4 | 5 | ```bash 6 | brew tap salesforce/cloud-guardrails https://github.com/salesforce/cloud-guardrails 7 | brew install cloud-guardrails 8 | ``` 9 | 10 | #### Option 2: Pip3 11 | 12 | ```bash 13 | pip3 install --user cloud-guardrails 14 | ``` 15 | 16 | #### Terraform 17 | 18 | * Install Terraform if you haven't already. I recommend using [tfenv](https://github.com/tfutils/tfenv), a Terraform version manager: 19 | 20 | ```bash 21 | brew install tfenv 22 | tfenv install 0.12.31 23 | ``` 24 | 25 | Now you can follow the rest of the tutorial. 26 | -------------------------------------------------------------------------------- /docs/requirements-docs.txt: -------------------------------------------------------------------------------- 1 | # Documentation 2 | mkdocs==1.2.1 3 | mkdocs-material==7.1.8 4 | mkdocs-material-extensions==1.0.1 5 | mkdocstrings==0.15.2 6 | Pygments==2.9.0 7 | pymdown-extensions==8.2 8 | pytkdocs==0.11.1 9 | mkdocs-table-reader-plugin==0.5 10 | 11 | # Docs 12 | # atomicwrites==1.4.0 13 | # distlib==0.3.1 14 | # filelock==3.0.12 15 | -------------------------------------------------------------------------------- /docs/summaries/0-all.md: -------------------------------------------------------------------------------- 1 | # All Policies 2 | 3 | {{ read_csv('summaries/all_policies.csv') }} 4 | -------------------------------------------------------------------------------- /docs/summaries/0-no-params.md: -------------------------------------------------------------------------------- 1 | # No Parameters 2 | 3 | {{ read_csv('summaries/no-params.csv') }} 4 | -------------------------------------------------------------------------------- /docs/summaries/README.md: -------------------------------------------------------------------------------- 1 | You can view the example CSV and Markdown formatted artifacts in this folder. 2 | 3 | CSV Files: 4 | 5 | * [Azure Policies with No Parameters](no-params.csv) 6 | * [Azure Policies with Optional Parameters](params-optional.csv) 7 | * [Azure Policies that Require Parameters](params-required.csv) 8 | 9 | Markdown files: 10 | * [Azure Policies with No Parameters](no-params.md) 11 | * [Azure Policies with Optional Parameters](params-optional.md) 12 | * [Azure Policies that Require Parameters](params-required.md) 13 | 14 | -------------------------------------------------------------------------------- /docs/tutorials/basic-key-vault.md: -------------------------------------------------------------------------------- 1 | ## Basic Tutorial: Key Vault Example 2 | 3 | You can also generate policies for a single service. Consider the example below where we generate Terraform for Key Vault only: 4 | 5 | ```bash 6 | cloud-guardrails generate-terraform --no-params \ 7 | --service "Key Vault" \ 8 | --subscription example 9 | ``` 10 | 11 |
12 | Click to expand! 13 |

14 | 15 | ```hcl 16 | locals { 17 | name_no_params = "example_NP_Audit" 18 | subscription_name_no_params = "example" 19 | management_group_no_params = "" 20 | enforcement_mode_no_params = false 21 | policy_ids_no_params = [ 22 | # ----------------------------------------------------------------------------------------------------------------- 23 | # Key Vault 24 | # ----------------------------------------------------------------------------------------------------------------- 25 | "c39ba22d-4428-4149-b981-70acb31fc383", # Azure Key Vault Managed HSM should have purge protection enabled 26 | "0b60c0b2-2dc2-4e1c-b5c9-abbed971de53", # Key vaults should have purge protection enabled 27 | "1e66c121-a66a-4b1f-9b83-0fd99bf0fc2d", # Key vaults should have soft delete enabled 28 | "55615ac9-af46-4a59-874e-391cc3dfb490", # Firewall should be enabled on Key Vault 29 | "152b15f7-8e1f-4c1f-ab71-8c010ba5dbc0", # Key Vault keys should have an expiration date 30 | "98728c90-32c7-4049-8429-847dc0f4fe37", # Key Vault secrets should have an expiration date 31 | "587c79fe-dd04-4a5e-9d0b-f89598c7261b", # Keys should be backed by a hardware security module (HSM) 32 | "5f0bc445-3935-4915-9981-011aa2b46147", # Private endpoint should be configured for Key Vault 33 | "75262d3e-ba4a-4f43-85f8-9f72c090e5e3", # Secrets should have content type set 34 | 35 | ] 36 | } 37 | 38 | # --------------------------------------------------------------------------------------------------------------------- 39 | # Azure Policy name lookups: 40 | # Because the policies are built-in, we can just look up their IDs by their names. 41 | # --------------------------------------------------------------------------------------------------------------------- 42 | data "azurerm_policy_definition" "no_params" { 43 | count = length(local.policy_ids_no_params) 44 | name = element(local.policy_ids_no_params, count.index) 45 | } 46 | 47 | locals { 48 | no_params_policy_definitions = flatten([tolist([ 49 | for definition in data.azurerm_policy_definition.no_params.*.id : 50 | map("policyDefinitionId", definition) 51 | ]) 52 | ]) 53 | } 54 | 55 | # --------------------------------------------------------------------------------------------------------------------- 56 | # Conditional data lookups: If the user supplies management group, look up the ID of the management group 57 | # --------------------------------------------------------------------------------------------------------------------- 58 | data "azurerm_management_group" "no_params" { 59 | count = local.management_group_no_params != "" ? 1 : 0 60 | display_name = local.management_group_no_params 61 | } 62 | 63 | ### If the user supplies subscription, look up the ID of the subscription 64 | data "azurerm_subscriptions" "no_params" { 65 | count = local.subscription_name_no_params != "" ? 1 : 0 66 | display_name_contains = local.subscription_name_no_params 67 | } 68 | 69 | locals { 70 | no_params_scope = local.management_group_no_params != "" ? data.azurerm_management_group.no_params[0].id : element(data.azurerm_subscriptions.no_params[0].subscriptions.*.id, 0) 71 | } 72 | 73 | # --------------------------------------------------------------------------------------------------------------------- 74 | # Policy Initiative 75 | # --------------------------------------------------------------------------------------------------------------------- 76 | resource "azurerm_policy_set_definition" "no_params" { 77 | name = local.name_no_params 78 | policy_type = "Custom" 79 | display_name = local.name_no_params 80 | description = local.name_no_params 81 | management_group_name = local.management_group_no_params == "" ? null : local.management_group_no_params 82 | policy_definitions = tostring(jsonencode(local.no_params_policy_definitions)) 83 | metadata = tostring(jsonencode({ 84 | category = local.name_no_params 85 | })) 86 | } 87 | 88 | # --------------------------------------------------------------------------------------------------------------------- 89 | # Azure Policy Assignments 90 | # Apply the Policy Initiative to the specified scope 91 | # --------------------------------------------------------------------------------------------------------------------- 92 | resource "azurerm_policy_assignment" "no_params" { 93 | name = local.name_no_params 94 | policy_definition_id = azurerm_policy_set_definition.no_params.id 95 | scope = local.no_params_scope 96 | enforcement_mode = local.enforcement_mode_no_params 97 | } 98 | 99 | # --------------------------------------------------------------------------------------------------------------------- 100 | # Outputs 101 | # --------------------------------------------------------------------------------------------------------------------- 102 | output "no_params_policy_assignment_ids" { 103 | value = azurerm_policy_assignment.no_params.id 104 | description = "The IDs of the Policy Assignments." 105 | } 106 | 107 | output "no_params_scope" { 108 | value = local.no_params_scope 109 | description = "The target scope - either the management group or subscription, depending on which parameters were supplied" 110 | } 111 | 112 | output "no_params_policy_set_definition_id" { 113 | value = azurerm_policy_set_definition.no_params.id 114 | description = "The ID of the Policy Set Definition." 115 | } 116 | 117 | output "no_params_count_of_policies_applied" { 118 | description = "The number of Policies applied as part of the Policy Initiative" 119 | value = length(local.policy_ids_no_params) 120 | } 121 | ``` 122 |

123 | -------------------------------------------------------------------------------- /docs/tutorials/parameters-required.md: -------------------------------------------------------------------------------- 1 | # Advanced: Parameters Required 2 | 3 | -------------------------------------------------------------------------------- /examples/parameters-config-example.yml: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # API Management 3 | # --------------------------------------------------------------------------------------------------------------------- 4 | API Management: 5 | API Management service should use a SKU that supports virtual networks: 6 | effect: Deny # Audit, Deny, Disabled. Default: Audit 7 | listOfAllowedSKUs: # Developer, Basic, Standard, Premium, Isolated, Consumption 8 | - Developer 9 | - Premium 10 | - Isolated 11 | # --------------------------------------------------------------------------------------------------------------------- 12 | # 13 | # --------------------------------------------------------------------------------------------------------------------- 14 | Kubernetes: 15 | Kubernetes cluster containers CPU and memory resource limits should not exceed the specified limits: 16 | effect: Audit # Audit, Deny, Disabled 17 | excludedNamespaces: 18 | - kube-system 19 | - gatekeeper-system 20 | - azure-arc 21 | namespaces: [] 22 | labelSelector: {} 23 | cpuLimit: "200m" 24 | memoryLimit: "1Gi" 25 | Kubernetes cluster containers should not share host process ID or host IPC namespace: 26 | effect: Audit # Audit, Deny, Disabled 27 | excludedNamespaces: 28 | - kube-system 29 | - gatekeeper-system 30 | - azure-arc 31 | namespaces: [ ] 32 | labelSelector: {} 33 | Kubernetes cluster containers should not use forbidden sysctl interfaces: 34 | effect: Audit # Audit, Deny, Disabled 35 | excludedNamespaces: 36 | - kube-system 37 | - gatekeeper-system 38 | - azure-arc 39 | namespaces: [ ] 40 | labelSelector: {} 41 | forbiddenSysctls: [ ] 42 | # Kubernetes cluster containers should only listen on allowed ports 43 | # Kubernetes cluster containers should only use allowed AppArmor profiles 44 | # Kubernetes cluster containers should only use allowed ProcMountType 45 | # Kubernetes cluster containers should only use allowed capabilities 46 | # Kubernetes cluster containers should only use allowed images 47 | # Kubernetes cluster containers should only use allowed seccomp profiles 48 | # Kubernetes cluster containers should run with a read only root file system 49 | # Kubernetes cluster pod FlexVolume volumes should only use allowed drivers 50 | # Kubernetes cluster pod hostPath volumes should only use allowed host paths 51 | # Kubernetes cluster pods and containers should only run with approved user and group 52 | # IDs 53 | # Kubernetes cluster pods and containers should only use allowed SELinux options 54 | # Kubernetes cluster pods should only use allowed volume types 55 | # Kubernetes cluster pods should only use approved host network and port range 56 | # Kubernetes cluster pods should use specified labels 57 | # Kubernetes cluster services should listen only on allowed ports 58 | # Kubernetes cluster should not allow privileged containers 59 | # Kubernetes clusters should be accessible only over HTTPS 60 | # Kubernetes clusters should not allow container privilege escalation 61 | # Kubernetes clusters should use internal load balancers 62 | # '[Preview]: Kubernetes cluster services should only use allowed external IPs' 63 | # '[Preview]: Kubernetes clusters should disable automounting API credentials' 64 | # '[Preview]: Kubernetes clusters should not grant CAP_SYS_ADMIN security capabilities' 65 | # '[Preview]: Kubernetes clusters should not use specific security capabilities' 66 | # '[Preview]: Kubernetes clusters should not use the default namespace' -------------------------------------------------------------------------------- /examples/terraform-demo-no-params/NP-all-table.csv: -------------------------------------------------------------------------------- 1 | Service,Policy Definition,Parameter Requirements,Audit Only,Azure Security Benchmark,CIS,CCMC L3,ISO 27001,NIST SP 800-53 R4,NIST SP 800-171 R2,HIPAA HITRUST 9.2,New Zealand ISM,Parameters,Link,ID 2 | Compute,Audit VMs that do not use managed disks,None,Yes,,7.1,,A.9.1.2,,,,,,https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Compute/VMRequireManagedDisk_Audit.json,06a78e20-9358-41c9-923c-fb736d382a4d 3 | Compute,Audit virtual machines without disaster recovery configured,None,Yes,,,,,CP-7,,1638.12b2Organizational.345 - 12.b,ESS-3,,https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Compute/RecoveryServices_DisasterRecovery_Audit.json,0015ea4d-51ff-4ce3-8d8c-f3f8f0179a56 4 | Compute,Require automatic OS image patching on Virtual Machine Scale Sets,None,No,,,,,,,,,,https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Compute/VMSSOSUpgradeHealthCheck_Deny.json,465f0161-0087-490a-9ad9-ad6217f4f43a 5 | Data Lake,Require encryption on Data Lake Store accounts,None,No,,,,,,,0304.09o3Organizational.1 - 09.o,,,https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Data%20Lake/DataLakeStoreEncryption_Deny.json,a7ff3161-0087-490a-9ad9-ad6217f4f43a 6 | General,Audit resource location matches resource group location,None,Yes,,,,,,,,,,https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/General/ResourcesInResourceGroupLocation_Audit.json,0a914e76-4921-4c19-b460-a2d36003525a 7 | Network,Gateway subnets should not be configured with a network security group,None,No,,,,,,,0894.01m2Organizational.7 - 01.m,,,https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Network/NetworkSecurityGroupOnGatewaySubnet_Deny.json,35f9c03a-cc27-418e-9c0c-539ff999d010 8 | Network,Network interfaces should disable IP forwarding,None,No,,,,,,,,,,https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Network/NetworkIPForwardingNic_Deny.json,88c0b9da-ce96-4b03-9635-f29a937e2900 9 | Network,Network interfaces should not have public IPs,None,No,,,,,,,,,,https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Network/NetworkPublicIPNic_Deny.json,83a86a26-fd1f-447c-b59d-e51f44264114 10 | -------------------------------------------------------------------------------- /examples/terraform-demo-no-params/NP-all-table.md: -------------------------------------------------------------------------------- 1 | | Service | Policy Definition | Parameter Requirements | Audit Only | Azure Security Benchmark | CIS | CCMC L3 | ISO 27001 | NIST SP 800-171 R2 | NIST SP 800-53 R4 | HIPAA HITRUST 9.2 | New Zealand ISM | Parameters | Link | ID | 2 | |-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------|--------------|----------------------------|-------|-----------|-------------|----------------------|---------------------|------------------------------------|-------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------| 3 | | Compute | [Audit VMs that do not use managed disks](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Compute/VMRequireManagedDisk_Audit.json) | None | Yes | | 7.1 | | A.9.1.2 | | | | | | https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Compute/VMRequireManagedDisk_Audit.json | 06a78e20-9358-41c9-923c-fb736d382a4d | 4 | | Compute | [Audit virtual machines without disaster recovery configured](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Compute/RecoveryServices_DisasterRecovery_Audit.json) | None | Yes | | | | | | CP-7 | 1638.12b2Organizational.345 - 12.b | ESS-3 | | https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Compute/RecoveryServices_DisasterRecovery_Audit.json | 0015ea4d-51ff-4ce3-8d8c-f3f8f0179a56 | 5 | | Compute | [Require automatic OS image patching on Virtual Machine Scale Sets](https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Compute/VMSSOSUpgradeHealthCheck_Deny.json) | None | No | | | | | | | | | | https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Compute/VMSSOSUpgradeHealthCheck_Deny.json | 465f0161-0087-490a-9ad9-ad6217f4f43a | 6 | | Data Lake | [Require encryption on Data Lake Store accounts](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Data%20Lake/DataLakeStoreEncryption_Deny.json) | None | No | | | | | | | 0304.09o3Organizational.1 - 09.o | | | https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Data%20Lake/DataLakeStoreEncryption_Deny.json | a7ff3161-0087-490a-9ad9-ad6217f4f43a | 7 | | General | [Audit resource location matches resource group location](https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/General/ResourcesInResourceGroupLocation_Audit.json) | None | Yes | | | | | | | | | | https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/General/ResourcesInResourceGroupLocation_Audit.json | 0a914e76-4921-4c19-b460-a2d36003525a | 8 | | Network | [Gateway subnets should not be configured with a network security group](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Network/NetworkSecurityGroupOnGatewaySubnet_Deny.json) | None | No | | | | | | | 0894.01m2Organizational.7 - 01.m | | | https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Network/NetworkSecurityGroupOnGatewaySubnet_Deny.json | 35f9c03a-cc27-418e-9c0c-539ff999d010 | 9 | | Network | [Network interfaces should disable IP forwarding](https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Network/NetworkIPForwardingNic_Deny.json) | None | No | | | | | | | | | | https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Network/NetworkIPForwardingNic_Deny.json | 88c0b9da-ce96-4b03-9635-f29a937e2900 | 10 | | Network | [Network interfaces should not have public IPs](https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Network/NetworkPublicIPNic_Deny.json) | None | No | | | | | | | | | | https://github.com/Azure/azure-policy/tree/master/built-in-policies/policyDefinitions/Network/NetworkPublicIPNic_Deny.json | 83a86a26-fd1f-447c-b59d-e51f44264114 | -------------------------------------------------------------------------------- /examples/terraform-demo-no-params/no_params.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | name_no_params = "example_NP_Audit" 3 | subscription_name_no_params = "example" 4 | management_group_no_params = "" 5 | enforcement_mode_no_params = false 6 | policy_ids_no_params = [ 7 | # ----------------------------------------------------------------------------------------------------------------- 8 | # Compute 9 | # ----------------------------------------------------------------------------------------------------------------- 10 | "06a78e20-9358-41c9-923c-fb736d382a4d", # Audit VMs that do not use managed disks 11 | "0015ea4d-51ff-4ce3-8d8c-f3f8f0179a56", # Audit virtual machines without disaster recovery configured 12 | "465f0161-0087-490a-9ad9-ad6217f4f43a", # Require automatic OS image patching on Virtual Machine Scale Sets 13 | 14 | # ----------------------------------------------------------------------------------------------------------------- 15 | # Data Lake 16 | # ----------------------------------------------------------------------------------------------------------------- 17 | "a7ff3161-0087-490a-9ad9-ad6217f4f43a", # Require encryption on Data Lake Store accounts 18 | 19 | # ----------------------------------------------------------------------------------------------------------------- 20 | # General 21 | # ----------------------------------------------------------------------------------------------------------------- 22 | "0a914e76-4921-4c19-b460-a2d36003525a", # Audit resource location matches resource group location 23 | 24 | # ----------------------------------------------------------------------------------------------------------------- 25 | # Network 26 | # ----------------------------------------------------------------------------------------------------------------- 27 | "35f9c03a-cc27-418e-9c0c-539ff999d010", # Gateway subnets should not be configured with a network security group 28 | "88c0b9da-ce96-4b03-9635-f29a937e2900", # Network interfaces should disable IP forwarding 29 | "83a86a26-fd1f-447c-b59d-e51f44264114", # Network interfaces should not have public IPs 30 | 31 | ] 32 | } 33 | 34 | # --------------------------------------------------------------------------------------------------------------------- 35 | # Azure Policy name lookups: 36 | # Because the policies are built-in, we can just look up their IDs by their names. 37 | # --------------------------------------------------------------------------------------------------------------------- 38 | data "azurerm_policy_definition" "no_params" { 39 | count = length(local.policy_ids_no_params) 40 | name = element(local.policy_ids_no_params, count.index) 41 | } 42 | 43 | locals { 44 | no_params_policy_definitions = flatten([tolist([ 45 | for definition in data.azurerm_policy_definition.no_params.*.id : 46 | map("policyDefinitionId", definition) 47 | ]) 48 | ]) 49 | } 50 | 51 | # --------------------------------------------------------------------------------------------------------------------- 52 | # Conditional data lookups: If the user supplies management group, look up the ID of the management group 53 | # --------------------------------------------------------------------------------------------------------------------- 54 | data "azurerm_management_group" "no_params" { 55 | count = local.management_group_no_params != "" ? 1 : 0 56 | display_name = local.management_group_no_params 57 | } 58 | 59 | ### If the user supplies subscription, look up the ID of the subscription 60 | data "azurerm_subscriptions" "no_params" { 61 | count = local.subscription_name_no_params != "" ? 1 : 0 62 | display_name_contains = local.subscription_name_no_params 63 | } 64 | 65 | locals { 66 | no_params_scope = local.management_group_no_params != "" ? data.azurerm_management_group.no_params[0].id : element(data.azurerm_subscriptions.no_params[0].subscriptions.*.id, 0) 67 | } 68 | 69 | # --------------------------------------------------------------------------------------------------------------------- 70 | # Policy Initiative 71 | # --------------------------------------------------------------------------------------------------------------------- 72 | resource "azurerm_policy_set_definition" "no_params" { 73 | name = local.name_no_params 74 | policy_type = "Custom" 75 | display_name = local.name_no_params 76 | description = local.name_no_params 77 | management_group_name = local.management_group_no_params == "" ? null : local.management_group_no_params 78 | policy_definitions = tostring(jsonencode(local.no_params_policy_definitions)) 79 | metadata = tostring(jsonencode({ 80 | category = local.name_no_params 81 | })) 82 | } 83 | 84 | # --------------------------------------------------------------------------------------------------------------------- 85 | # Azure Policy Assignments 86 | # Apply the Policy Initiative to the specified scope 87 | # --------------------------------------------------------------------------------------------------------------------- 88 | resource "azurerm_policy_assignment" "no_params" { 89 | name = local.name_no_params 90 | policy_definition_id = azurerm_policy_set_definition.no_params.id 91 | scope = local.no_params_scope 92 | enforcement_mode = local.enforcement_mode_no_params 93 | } 94 | 95 | # --------------------------------------------------------------------------------------------------------------------- 96 | # Outputs 97 | # --------------------------------------------------------------------------------------------------------------------- 98 | output "no_params_policy_assignment_ids" { 99 | value = azurerm_policy_assignment.no_params.id 100 | description = "The IDs of the Policy Assignments." 101 | } 102 | 103 | output "no_params_scope" { 104 | value = local.no_params_scope 105 | description = "The target scope - either the management group or subscription, depending on which parameters were supplied" 106 | } 107 | 108 | output "no_params_policy_set_definition_id" { 109 | value = azurerm_policy_set_definition.no_params.id 110 | description = "The ID of the Policy Set Definition." 111 | } 112 | 113 | output "no_params_count_of_policies_applied" { 114 | description = "The number of Policies applied as part of the Policy Initiative" 115 | value = length(local.policy_ids_no_params) 116 | } -------------------------------------------------------------------------------- /examples/terraform-demo-no-params/provider.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | version = "=2.56.0" 3 | features {} 4 | } -------------------------------------------------------------------------------- /examples/terraform-demo-no-params/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.0" 3 | } 4 | -------------------------------------------------------------------------------- /examples/terraform-demo-params-optional/provider.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | version = "=2.56.0" 3 | features {} 4 | } -------------------------------------------------------------------------------- /examples/terraform-demo-params-optional/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.0" 3 | } 4 | -------------------------------------------------------------------------------- /examples/terraform-demo-params-required/provider.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | version = "=2.56.0" 3 | features {} 4 | } -------------------------------------------------------------------------------- /examples/terraform-demo-params-required/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.0" 3 | } 4 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Cloud Guardrails 2 | site_url: https://cloud-guardrails.readthedocs.io/ 3 | repo_url: https://github.com/salesforce/cloud-guardrails/ 4 | theme: material 5 | use_directory_urls: true 6 | markdown_extensions: 7 | - codehilite 8 | - tables 9 | - pymdownx.superfences 10 | - admonition 11 | - pymdownx.details 12 | plugins: 13 | - search 14 | - table-reader: 15 | data_path: "docs" 16 | - mkdocstrings: 17 | default_handler: python 18 | handlers: 19 | python: 20 | rendering: 21 | show_source: true 22 | watch: 23 | - cloud_guardrails/ 24 | extra_css: 25 | - custom.css 26 | 27 | nav: 28 | - Home: 'index.md' 29 | - Installation: 'installation.md' 30 | - Cheatsheet: 'cheatsheet.md' 31 | 32 | - "Tutorials": 33 | - Basic - No Parameters: 'tutorials/basic.md' 34 | - Basic - Single Service: 'tutorials/basic-key-vault.md' 35 | - Intermediate - Parameters Optional: 'tutorials/parameters-optional.md' 36 | - Advanced - Parameters Required: 'tutorials/parameters-required.md' 37 | - Selecting Policies using the Config File: 'tutorials/policy-selection-config.md' 38 | 39 | - "Contributing": 40 | - Contributing: 'contributing/contributing.md' 41 | 42 | - "Compliance Summaries": 43 | - All Policies: 'summaries/0-all.md' 44 | - No Parameters: 'summaries/no-params.md' 45 | - Parameters Optional: 'summaries/params-optional.md' 46 | - Parameters Required: 'summaries/params-required.md' 47 | 48 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Formatting 2 | black==20.8b1 3 | 4 | # Security tests 5 | bandit==1.7.0 6 | 7 | # Testing 8 | nose==1.3.7 9 | pytest==6.2.5 10 | coverage==5.5 11 | pylint==2.10.2 12 | invoke==1.6.0 13 | 14 | # Terraform testing 15 | bc-python-hcl2==0.3.21 16 | 17 | # Documentation 18 | mkdocs==1.2.2 19 | mkdocs-material==7.2.6 20 | mkdocs-material-extensions==1.0.3 21 | mkdocstrings==0.15.2 22 | Pygments==2.10.0 23 | pymdown-extensions==8.2 24 | pytkdocs==0.11.1 25 | mkdocs-table-reader-plugin==0.6 26 | 27 | # Automation to create the excel spreadsheets 28 | openpyxl==3.0.9 29 | pandas==1.3.2 30 | lxml==4.6.5 31 | 32 | # Docs 33 | # atomicwrites==1.4.0 34 | # distlib==0.3.1 35 | # filelock==3.0.12 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.0.1 2 | click_option_group==0.5.3 3 | PyYAML==5.4.1 4 | # Required for printing things 5 | jinja2==3.0.1 6 | tabulate==0.8.9 7 | ruamel.yaml==0.17.16 8 | colorama==0.4.4 9 | # Scrapers 10 | beautifulsoup4==4.10.0 11 | requests==2.26.0 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | exe = True 3 | tests = test/, test/command/, test/iam_definition, test/shared, test/templates, test/terraform 4 | verbosity=2 5 | 6 | [tool:pytest] 7 | testpaths = test test/command test/iam_definition test/shared test/templates test/terraform 8 | python_files=test/*/test_*.py 9 | norecursedirs = .svn _build tmp* __pycache__ 10 | 11 | # Exclude: __pycache__ / .pyc 12 | [coverage:run] 13 | omit = 14 | # omit anything in a .local directory anywhere 15 | */.local/* 16 | utils/* 17 | */virtualenv/* 18 | */venv/* 19 | */.venv/* 20 | # Where we want to skip 21 | cloud_guardrails/shared/azure-policy/* 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup script""" 2 | import setuptools 3 | import os 4 | import re 5 | 6 | HERE = os.path.abspath(os.path.dirname(__file__)) 7 | VERSION_RE = re.compile(r"""__version__ = ['"]([0-9.]+)['"]""") 8 | TESTS_REQUIRE = ["coverage", "nose", "pytest"] 9 | DESCRIPTION = "" 10 | REQUIRED_PACKAGES = [ 11 | "click", 12 | "click_option_group", 13 | "pyyaml", 14 | "jinja2", 15 | "tabulate", 16 | "colorama", 17 | "ruamel.yaml", 18 | "beautifulsoup4", 19 | "requests", 20 | ] 21 | 22 | 23 | def get_version(): 24 | init = open(os.path.join(HERE, "cloud_guardrails", "bin", "version.py")).read() 25 | return VERSION_RE.search(init).group(1) 26 | 27 | 28 | def get_description(): 29 | return open( 30 | os.path.join(os.path.abspath(HERE), "README.md"), encoding="utf-8" 31 | ).read() 32 | 33 | 34 | setuptools.setup( 35 | name="cloud-guardrails", 36 | include_package_data=True, 37 | version=get_version(), 38 | author="Kinnaird McQuade", 39 | author_email="kinnairdm@gmail.com", 40 | description=DESCRIPTION, 41 | long_description=get_description(), 42 | long_description_content_type="text/markdown", 43 | url="https://github.com/salesforce/cloud-guardrails", 44 | packages=setuptools.find_packages(exclude=["test*"]), 45 | tests_require=TESTS_REQUIRE, 46 | install_requires=REQUIRED_PACKAGES, 47 | classifiers=[ 48 | "Programming Language :: Python :: 3", 49 | "License :: OSI Approved :: MIT License", 50 | "Operating System :: OS Independent", 51 | ], 52 | entry_points={"console_scripts": "cloud-guardrails=cloud_guardrails.bin.cli:main"}, 53 | zip_safe=True, 54 | keywords="azure security", 55 | python_requires=">=3.7", 56 | ) 57 | -------------------------------------------------------------------------------- /test/command/no_params.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | name_no_params = "example_NP_Enforce" 3 | subscription_name_no_params = "example" 4 | management_group_no_params = "" 5 | enforcement_mode_no_params = true 6 | policy_ids_no_params = [ 7 | # ----------------------------------------------------------------------------------------------------------------- 8 | # Compute 9 | # ----------------------------------------------------------------------------------------------------------------- 10 | "06a78e20-9358-41c9-923c-fb736d382a4d", # Audit VMs that do not use managed disks 11 | "0015ea4d-51ff-4ce3-8d8c-f3f8f0179a56", # Audit virtual machines without disaster recovery configured 12 | "465f0161-0087-490a-9ad9-ad6217f4f43a", # Require automatic OS image patching on Virtual Machine Scale Sets 13 | 14 | # ----------------------------------------------------------------------------------------------------------------- 15 | # Data Lake 16 | # ----------------------------------------------------------------------------------------------------------------- 17 | "a7ff3161-0087-490a-9ad9-ad6217f4f43a", # Require encryption on Data Lake Store accounts 18 | 19 | # ----------------------------------------------------------------------------------------------------------------- 20 | # General 21 | # ----------------------------------------------------------------------------------------------------------------- 22 | "0a914e76-4921-4c19-b460-a2d36003525a", # Audit resource location matches resource group location 23 | 24 | # ----------------------------------------------------------------------------------------------------------------- 25 | # Network 26 | # ----------------------------------------------------------------------------------------------------------------- 27 | "35f9c03a-cc27-418e-9c0c-539ff999d010", # Gateway subnets should not be configured with a network security group 28 | "88c0b9da-ce96-4b03-9635-f29a937e2900", # Network interfaces should disable IP forwarding 29 | "83a86a26-fd1f-447c-b59d-e51f44264114", # Network interfaces should not have public IPs 30 | 31 | ] 32 | } 33 | 34 | # --------------------------------------------------------------------------------------------------------------------- 35 | # Azure Policy name lookups: 36 | # Because the policies are built-in, we can just look up their IDs by their names. 37 | # --------------------------------------------------------------------------------------------------------------------- 38 | data "azurerm_policy_definition" "no_params" { 39 | count = length(local.policy_ids_no_params) 40 | name = element(local.policy_ids_no_params, count.index) 41 | } 42 | 43 | locals { 44 | no_params_policy_definitions = flatten([tolist([ 45 | for definition in data.azurerm_policy_definition.no_params.*.id : 46 | map("policyDefinitionId", definition) 47 | ]) 48 | ]) 49 | } 50 | 51 | # --------------------------------------------------------------------------------------------------------------------- 52 | # Conditional data lookups: If the user supplies management group, look up the ID of the management group 53 | # --------------------------------------------------------------------------------------------------------------------- 54 | data "azurerm_management_group" "no_params" { 55 | count = local.management_group_no_params != "" ? 1 : 0 56 | display_name = local.management_group_no_params 57 | } 58 | 59 | ### If the user supplies subscription, look up the ID of the subscription 60 | data "azurerm_subscriptions" "no_params" { 61 | count = local.subscription_name_no_params != "" ? 1 : 0 62 | display_name_contains = local.subscription_name_no_params 63 | } 64 | 65 | locals { 66 | no_params_scope = local.management_group_no_params != "" ? data.azurerm_management_group.no_params[0].id : element(data.azurerm_subscriptions.no_params[0].subscriptions.*.id, 0) 67 | } 68 | 69 | # --------------------------------------------------------------------------------------------------------------------- 70 | # Policy Initiative 71 | # --------------------------------------------------------------------------------------------------------------------- 72 | resource "azurerm_policy_set_definition" "no_params" { 73 | name = local.name_no_params 74 | policy_type = "Custom" 75 | display_name = local.name_no_params 76 | description = local.name_no_params 77 | management_group_name = local.management_group_no_params == "" ? null : local.management_group_no_params 78 | policy_definitions = tostring(jsonencode(local.no_params_policy_definitions)) 79 | metadata = tostring(jsonencode({ 80 | category = local.name_no_params 81 | })) 82 | } 83 | 84 | # --------------------------------------------------------------------------------------------------------------------- 85 | # Azure Policy Assignments 86 | # Apply the Policy Initiative to the specified scope 87 | # --------------------------------------------------------------------------------------------------------------------- 88 | resource "azurerm_policy_assignment" "no_params" { 89 | name = local.name_no_params 90 | policy_definition_id = azurerm_policy_set_definition.no_params.id 91 | scope = local.no_params_scope 92 | enforcement_mode = local.enforcement_mode_no_params 93 | } 94 | 95 | # --------------------------------------------------------------------------------------------------------------------- 96 | # Outputs 97 | # --------------------------------------------------------------------------------------------------------------------- 98 | output "no_params_policy_assignment_ids" { 99 | value = azurerm_policy_assignment.no_params.id 100 | description = "The IDs of the Policy Assignments." 101 | } 102 | 103 | output "no_params_scope" { 104 | value = local.no_params_scope 105 | description = "The target scope - either the management group or subscription, depending on which parameters were supplied" 106 | } 107 | 108 | output "no_params_policy_set_definition_id" { 109 | value = azurerm_policy_set_definition.no_params.id 110 | description = "The ID of the Policy Set Definition." 111 | } 112 | 113 | output "no_params_count_of_policies_applied" { 114 | description = "The number of Policies applied as part of the Policy Initiative" 115 | value = length(local.policy_ids_no_params) 116 | } -------------------------------------------------------------------------------- /test/command/test_create_parameters_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import yaml 4 | import unittest 5 | import time 6 | from click.testing import CliRunner 7 | from cloud_guardrails.command.create_parameters_file import create_parameters_file 8 | from cloud_guardrails.shared import utils 9 | 10 | 11 | class CreateParametersFileTestCase(unittest.TestCase): 12 | def setUp(self): 13 | self.runner = CliRunner() 14 | 15 | def test_create_parameters_file_with_click(self): 16 | """command.create_parameters_file: should return exit code 0""" 17 | result = self.runner.invoke(create_parameters_file, ["--help"]) 18 | print(result.output) 19 | self.assertTrue(result.exit_code == 0) 20 | 21 | def test_create_optional_parameters_file(self): 22 | parameters_file = os.path.join(os.path.dirname(__file__), "parameters-optional.yml") 23 | args = ["-o", parameters_file, "--optional-only"] 24 | result = self.runner.invoke(create_parameters_file, args) 25 | # print(result.output) 26 | self.assertTrue(result.exit_code == 0) 27 | content = utils.read_yaml_file(parameters_file) 28 | # os.remove(parameters_file) 29 | # print(json.dumps(content, indent=4)) 30 | print(json.dumps(content.get("Synapse"), indent=4)) 31 | # These expected results are meant to show the structure as well. There are other services as top level keys. 32 | # expected_results = { 33 | # "Synapse": { 34 | # "Auditing on Synapse workspace should be enabled": { 35 | # "effect": "AuditIfNotExists", 36 | # "setting": "enabled" 37 | # } 38 | # } 39 | # } 40 | self.assertTrue(content["Synapse"]["Auditing on Synapse workspace should be enabled"]["effect"] == "AuditIfNotExists") 41 | self.assertTrue(content["Synapse"]["Synapse workspace auditing settings should have action groups configured to capture critical activities"]["effect"] == "AuditIfNotExists") 42 | # self.assertDictEqual(content.get("Synapse"), expected_results.get("Synapse")) 43 | 44 | def test_create_optional_parameters_file_enforce(self): 45 | parameters_file = os.path.join(os.path.dirname(__file__), "parameters-optional-enforce.yml") 46 | args = ["-o", parameters_file, "--optional-only", "--enforce"] 47 | result = self.runner.invoke(create_parameters_file, args) 48 | self.assertTrue(result.exit_code == 0) 49 | content = utils.read_yaml_file(parameters_file) 50 | policy_to_check = content["Storage"]["Storage account public access should be disallowed"] 51 | print(json.dumps(policy_to_check, indent=4)) 52 | self.assertEqual(policy_to_check["effect"], "deny") 53 | 54 | def test_create_required_parameters_file(self): 55 | parameters_file = os.path.join(os.path.dirname(__file__), "parameters-required.yml") 56 | args = ["-o", parameters_file, "--required-only"] 57 | result = self.runner.invoke(create_parameters_file, args) 58 | print(result.output) 59 | self.assertTrue(result.exit_code == 0) 60 | content = utils.read_yaml_file(parameters_file) 61 | os.remove(parameters_file) 62 | # print(json.dumps(content, indent=4)) 63 | # These expected results are meant to show the structure as well. There are other services as top level keys. 64 | expected_results = { 65 | "Tags": { 66 | "Require a tag and its value on resource groups": { 67 | "tagName": None, 68 | "tagValue": None 69 | }, 70 | "Require a tag and its value on resources": { 71 | "tagName": None, 72 | "tagValue": None 73 | }, 74 | "Require a tag on resource groups": { 75 | "tagName": None 76 | }, 77 | "Require a tag on resources": { 78 | "tagName": None 79 | } 80 | } 81 | } 82 | self.assertDictEqual(content.get("Tags"), expected_results.get("Tags")) 83 | 84 | -------------------------------------------------------------------------------- /test/command/test_describe_policy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import yaml 4 | import unittest 5 | import time 6 | from click.testing import CliRunner 7 | from cloud_guardrails.command.describe_policy import describe_policy 8 | from cloud_guardrails.shared import utils 9 | 10 | 11 | class DescribePolicyTestCase(unittest.TestCase): 12 | def setUp(self): 13 | self.runner = CliRunner() 14 | 15 | def test_describe_policy_with_click(self): 16 | """command.describe_policy: should return exit code 0""" 17 | result = self.runner.invoke(describe_policy, ["--help"]) 18 | print(result.output) 19 | self.assertTrue(result.exit_code == 0) 20 | 21 | def test_describe_policy_output_by_display_name(self): 22 | display_name = "Service Bus Premium namespaces should use a customer-managed key for encryption" 23 | args = ["--name", display_name] 24 | result = self.runner.invoke(describe_policy, args) 25 | print(result.output) 26 | self.assertTrue("295fc8b1-dc9f-4f53-9c61-3f313ceab40a" in result.output) 27 | 28 | def test_describe_policy_output_by_id(self): 29 | args = ["--id", "295fc8b1-dc9f-4f53-9c61-3f313ceab40a"] 30 | result = self.runner.invoke(describe_policy, args) 31 | print(result.output) 32 | # GH #60 - describe-policy should output the GitHub link 33 | self.assertTrue("github.com" in result.output) 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/command/test_list_policies.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import json 4 | import unittest 5 | from click.testing import CliRunner 6 | from cloud_guardrails.command.list_policies import list_policies 7 | from cloud_guardrails.shared import utils 8 | 9 | 10 | class ListPoliciesClickUnitTests(unittest.TestCase): 11 | def setUp(self): 12 | self.runner = CliRunner() 13 | 14 | def test_list_services_command_with_click_help(self): 15 | """command.generate_terraform: should return exit code 0""" 16 | result = self.runner.invoke(list_policies, ["--help"]) 17 | self.assertTrue(result.exit_code == 0) 18 | 19 | def test_list_services_command_with_click_output_stdout(self): 20 | result = self.runner.invoke(list_policies, ["--service", "Key Vault", "--all-policies"]) 21 | self.assertTrue(result.exit_code == 0) 22 | print(result.output) 23 | # Ensure that it is not formatted in YAML 24 | self.assertTrue("Key Vault:" not in result.output) 25 | # Ensure that it has one of the policy names we expect 26 | self.assertTrue("Key vaults should have purge protection enabled" in result.output) 27 | 28 | def test_list_services_command_with_click_output_yaml(self): 29 | result = self.runner.invoke(list_policies, ["--service", "Key Vault", "--format", "yaml", "--all-policies"]) 30 | self.assertTrue(result.exit_code == 0) 31 | print(result.output) 32 | # Ensure that IS not formatted in YAML, where we expect "Key Vault:" to be one of the keys 33 | self.assertTrue("Key Vault:" in result.output) 34 | # Ensure that it has one of the policy names we expect 35 | yaml_results = yaml.safe_load(result.output) 36 | print(yaml_results.keys()) 37 | self.assertTrue("Key Vault" in yaml_results.keys()) 38 | self.assertTrue("Key vaults should have purge protection enabled" in yaml_results.get("Key Vault")) 39 | 40 | def test_list_services_command_with_click_with_parameters(self): 41 | result = self.runner.invoke(list_policies, ["--service", "Kubernetes", "--format", "yaml", "--params-optional"]) 42 | print(result.output) 43 | self.assertTrue(result.exit_code == 0) 44 | print(result.output) 45 | # # Ensure that IS not formatted in YAML, where we expect "Key Vault:" to be one of the keys 46 | result = self.runner.invoke(list_policies, ["--service", "Key Vault", "--format", "yaml", "--params-optional"]) 47 | self.assertTrue(result.exit_code == 0) 48 | print(result.output) 49 | self.assertTrue("Key Vault:" in result.output) 50 | # # Ensure that it has one of the policy names we expect 51 | # yaml_results = yaml.safe_load(result.output) 52 | # print(yaml_results.keys()) 53 | # self.assertTrue("Key Vault" in yaml_results.keys()) 54 | # self.assertTrue("Key vaults should have purge protection enabled" in yaml_results.get("Key Vault")) 55 | # 56 | -------------------------------------------------------------------------------- /test/command/test_list_services.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import unittest 4 | from click.testing import CliRunner 5 | from cloud_guardrails.command.list_services import list_services 6 | from cloud_guardrails.shared import utils 7 | 8 | 9 | class ListServicesClickUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | self.runner = CliRunner() 12 | 13 | def test_list_services_command_with_click_help(self): 14 | """command.generate_terraform: should return exit code 0""" 15 | result = self.runner.invoke(list_services, ["--help"]) 16 | self.assertTrue(result.exit_code == 0) 17 | 18 | def test_list_services_command_with_click_output(self): 19 | result = self.runner.invoke(list_services) 20 | self.assertTrue(result.exit_code == 0) 21 | print(result.output) 22 | expected_services = utils.get_service_names() 23 | for service in expected_services: 24 | self.assertTrue(service in result.output) 25 | -------------------------------------------------------------------------------- /test/files/ASC_Storage_DisallowPublicBlobAccess_Audit.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "displayName": "Storage account public access should be disallowed", 4 | "policyType": "BuiltIn", 5 | "mode": "Indexed", 6 | "description": "Anonymous public read access to containers and blobs in Azure Storage is a convenient way to share data but might present security risks. To prevent data breaches caused by undesired anonymous access, Microsoft recommends preventing public access to a storage account unless your scenario requires it.", 7 | "metadata": { 8 | "version": "2.0.1-preview", 9 | "category": "Storage", 10 | "preview": true 11 | }, 12 | "parameters": { 13 | "effect": { 14 | "type": "string", 15 | "defaultValue": "audit", 16 | "allowedValues": [ 17 | "audit", 18 | "deny", 19 | "disabled" 20 | ], 21 | "metadata": { 22 | "displayName": "Effect", 23 | "description": "The effect determines what happens when the policy rule is evaluated to match" 24 | } 25 | } 26 | }, 27 | "policyRule": { 28 | "if": { 29 | "allOf": [ 30 | { 31 | "field": "type", 32 | "equals": "Microsoft.Storage/storageAccounts" 33 | }, 34 | { 35 | "field": "id", 36 | "notContains": "/resourceGroups/databricks-rg-" 37 | }, 38 | { 39 | "not": { 40 | "field":"Microsoft.Storage/storageAccounts/allowBlobPublicAccess", 41 | "equals": "false" 42 | } 43 | } 44 | ] 45 | }, 46 | "then": { 47 | "effect": "[parameters('effect')]" 48 | } 49 | } 50 | }, 51 | "id": "/providers/Microsoft.Authorization/policyDefinitions/4fa4b6c0-31ca-4c0d-b10d-24b96f62a751", 52 | "name": "4fa4b6c0-31ca-4c0d-b10d-24b96f62a751" 53 | } -------------------------------------------------------------------------------- /test/files/ApiManagement_AllowedVNETSkus_AuditDeny.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "displayName": "API Management service should use a SKU that supports virtual networks", 4 | "policyType": "BuiltIn", 5 | "mode": "Indexed", 6 | "description": "With supported SKUs of API Management, deploying service into a virtual network unlocks advanced API Management networking and security features which provides you greater control over your network security configuration. Learn more at: https://aka.ms/apimvnet.", 7 | "metadata": { 8 | "version": "1.0.0", 9 | "category": "API Management" 10 | }, 11 | "parameters": { 12 | "effect": { 13 | "type": "String", 14 | "metadata": { 15 | "displayName": "Effect", 16 | "description": "Enable or disable the execution of the policy" 17 | }, 18 | "allowedValues": [ 19 | "Audit", 20 | "Deny", 21 | "Disabled" 22 | ], 23 | "defaultValue": "Audit" 24 | }, 25 | "listOfAllowedSKUs": { 26 | "type": "Array", 27 | "metadata": { 28 | "description": "The list of SKUs that can be specified for Azure API Management service.", 29 | "displayName": "Allowed SKUs" 30 | }, 31 | "allowedValues": [ 32 | "Developer", 33 | "Basic", 34 | "Standard", 35 | "Premium", 36 | "Isolated", 37 | "Consumption" 38 | ], 39 | "defaultValue": [ 40 | "Developer", 41 | "Premium", 42 | "Isolated" 43 | ] 44 | } 45 | }, 46 | "policyRule": { 47 | "if": { 48 | "allOf": [ 49 | { 50 | "field": "type", 51 | "equals": "Microsoft.ApiManagement/service" 52 | }, 53 | { 54 | "not": { 55 | "field": "Microsoft.ApiManagement/service/sku.name", 56 | "in": "[parameters('listOfAllowedSKUs')]" 57 | } 58 | } 59 | ] 60 | }, 61 | "then": { 62 | "effect": "[parameters('effect')]" 63 | } 64 | } 65 | }, 66 | "id": "/providers/Microsoft.Authorization/policyDefinitions/73ef9241-5d81-4cd4-b483-8443d1730fe5", 67 | "name": "73ef9241-5d81-4cd4-b483-8443d1730fe5" 68 | } -------------------------------------------------------------------------------- /test/files/Automation_AuditUnencryptedVars_Audit.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "displayName": "Automation account variables should be encrypted", 4 | "policyType": "BuiltIn", 5 | "mode": "All", 6 | "description": "It is important to enable encryption of Automation account variable assets when storing sensitive data", 7 | "metadata": { 8 | "version": "1.1.0", 9 | "category": "Automation" 10 | }, 11 | "parameters": { 12 | "effect": { 13 | "type": "string", 14 | "defaultValue": "Audit", 15 | "allowedValues": [ 16 | "Audit", 17 | "Deny", 18 | "Disabled" 19 | ], 20 | "metadata": { 21 | "displayName": "Effect", 22 | "description": "The effect determines what happens when the policy rule is evaluated to match" 23 | } 24 | } 25 | }, 26 | "policyRule": { 27 | "if": { 28 | "allOf": [ 29 | { 30 | "field": "type", 31 | "equals": "Microsoft.Automation/automationAccounts/variables" 32 | }, 33 | { 34 | "field": "Microsoft.Automation/automationAccounts/variables/isEncrypted", 35 | "notEquals": "true" 36 | } 37 | ] 38 | }, 39 | "then": { 40 | "effect": "[parameters('effect')]" 41 | } 42 | } 43 | }, 44 | "id": "/providers/Microsoft.Authorization/policyDefinitions/3657f5a0-770e-44a3-b44e-9431ba1e9735", 45 | "name": "3657f5a0-770e-44a3-b44e-9431ba1e9735" 46 | } -------------------------------------------------------------------------------- /test/files/Cosmos_DisableMetadata_Append.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "displayName": "Azure Cosmos DB key based metadata write access should be disabled", 4 | "policyType": "BuiltIn", 5 | "mode": "Indexed", 6 | "description": "This policy enables you to ensure all Azure Cosmos DB accounts disable key based metadata write access.", 7 | "metadata": { 8 | "version": "1.0.0", 9 | "category": "Cosmos DB" 10 | }, 11 | "parameters": {}, 12 | "policyRule": { 13 | "if": { 14 | "allOf":[ 15 | { 16 | "field": "type", 17 | "equals": "Microsoft.DocumentDB/databaseAccounts" 18 | }, 19 | { 20 | "field": "Microsoft.DocumentDB/databaseAccounts/disableKeyBasedMetadataWriteAccess", 21 | "notEquals": true 22 | } 23 | ] 24 | }, 25 | "then": { 26 | "effect": "append", 27 | "details": [{ 28 | "field": "Microsoft.DocumentDB/databaseAccounts/disableKeyBasedMetadataWriteAccess", 29 | "value": true 30 | }] 31 | } 32 | } 33 | }, 34 | "id": "/providers/Microsoft.Authorization/policyDefinitions/4750c32b-89c0-46af-bfcb-2e4541a818d5", 35 | "name": "4750c32b-89c0-46af-bfcb-2e4541a818d5" 36 | } -------------------------------------------------------------------------------- /test/files/PrivateLink_PublicNetworkAccess_Modify.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "displayName": "Configure App Configuration to disable public network access", 4 | "description": "Disable public network access for App Configuration so that it isn't accessible over the public internet. This configuration helps protect them against data leakage risks. You can limit exposure of the your resources by creating private endpoints instead. Learn more at: https://aka.ms/appconfig/private-endpoint.", 5 | "metadata": { 6 | "version": "1.0.0", 7 | "category": "App Configuration" 8 | }, 9 | "policyType": "BuiltIn", 10 | "mode": "Indexed", 11 | "parameters": { 12 | "effect": { 13 | "type": "String", 14 | "metadata": { 15 | "displayName": "Effect", 16 | "description": "Enable or disable the execution of the policy" 17 | }, 18 | "allowedValues": [ 19 | "Modify", 20 | "Disabled" 21 | ], 22 | "defaultValue": "Modify" 23 | } 24 | }, 25 | "policyRule": { 26 | "if": { 27 | "allOf": [ 28 | { 29 | "field": "type", 30 | "equals": "Microsoft.AppConfiguration/configurationStores" 31 | }, 32 | { 33 | "field": "Microsoft.AppConfiguration/configurationStores/publicNetworkAccess", 34 | "notEquals": "Disabled" 35 | } 36 | ] 37 | }, 38 | "then": { 39 | "effect": "[parameters('effect')]", 40 | "details": { 41 | "roleDefinitionIds": [ 42 | "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" 43 | ], 44 | "conflictEffect": "audit", 45 | "operations": [ 46 | { 47 | "condition": "[greater(requestContext().apiVersion, '2019-10-01')]", 48 | "operation": "addOrReplace", 49 | "field": "Microsoft.AppConfiguration/configurationStores/publicNetworkAccess", 50 | "value": "Disabled" 51 | } 52 | ] 53 | } 54 | } 55 | } 56 | }, 57 | "id": "/providers/Microsoft.Authorization/policyDefinitions/73290fa2-dfa7-4bbb-945d-a5e23b75df2c", 58 | "name": "73290fa2-dfa7-4bbb-945d-a5e23b75df2c" 59 | } -------------------------------------------------------------------------------- /test/files/SqlDBAuditing_Audit.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "displayName": "Auditing on SQL server should be enabled", 4 | "policyType": "BuiltIn", 5 | "mode": "Indexed", 6 | "description": "Auditing on your SQL Server should be enabled to track database activities across all databases on the server and save them in an audit log.", 7 | "metadata": { 8 | "version": "2.0.0", 9 | "category": "SQL" 10 | }, 11 | "parameters": { 12 | "effect": { 13 | "type": "string", 14 | "defaultValue": "AuditIfNotExists", 15 | "allowedValues": [ 16 | "AuditIfNotExists", 17 | "Disabled" 18 | ], 19 | "metadata": { 20 | "displayName": "Effect", 21 | "description": "Enable or disable the execution of the policy" 22 | } 23 | }, 24 | "setting": { 25 | "type": "String", 26 | "metadata": { 27 | "displayName": "Desired Auditing setting" 28 | }, 29 | "defaultValue": "enabled", 30 | "allowedValues": [ 31 | "enabled", 32 | "disabled" 33 | ] 34 | } 35 | }, 36 | "policyRule": { 37 | "if": { 38 | "allOf": [ 39 | { 40 | "field": "type", 41 | "equals": "Microsoft.Sql/servers" 42 | }, 43 | { 44 | "field": "kind", 45 | "notContains": "analytics" 46 | } 47 | ] 48 | }, 49 | "then": { 50 | "effect": "[parameters('effect')]", 51 | "details": { 52 | "type": "Microsoft.Sql/servers/auditingSettings", 53 | "name": "default", 54 | "existenceCondition": { 55 | "field": "Microsoft.Sql/auditingSettings.state", 56 | "equals": "[parameters('setting')]" 57 | } 58 | } 59 | } 60 | } 61 | }, 62 | "id": "/providers/Microsoft.Authorization/policyDefinitions/a6fb4358-5bf4-4ad7-ba82-2cd2f41ce5e9", 63 | "name": "a6fb4358-5bf4-4ad7-ba82-2cd2f41ce5e9" 64 | } -------------------------------------------------------------------------------- /test/files/bad-config.yml: -------------------------------------------------------------------------------- 1 | # Specify Azure Policy Definition displayNames that you want to exclude from the results 2 | exclude_policies: 3 | Data Factory: 4 | - [Preview]: Azure Data Factory should use a Git repository for source control 5 | # - "[Preview]: Azure Data Factory should use a Git repository for source control" 6 | 7 | -------------------------------------------------------------------------------- /test/files/config-match-keywords.yml: -------------------------------------------------------------------------------- 1 | match_only_keywords: 2 | - "private link" -------------------------------------------------------------------------------- /test/files/example-config.yml: -------------------------------------------------------------------------------- 1 | #### 2 | # match_only_keywords: Use this to only apply policies that match any of these keywords 3 | # exclude_services: Specify services that you want to exclude entirely. 4 | # exclude_policies: Specify Azure Policy Definition displayNames that you want to exclude from the results, sorted by service 5 | #### 6 | 7 | # Use this to only apply policies that match any of these keywords 8 | # Example: "encrypt", "SQL", "HTTP" 9 | match_only_keywords: 10 | - "private link" 11 | 12 | 13 | # Specify services that you want to exclude entirely. 14 | # Uncomment the services mentioned below if you want to exclude them. 15 | exclude_services: 16 | - "" 17 | #- "API Management" 18 | #- "API for FHIR" 19 | #- "App Configuration" 20 | #- "App Platform" 21 | #- "App Service" 22 | #- "Attestation" 23 | #- "Automanage" 24 | #- "Automation" 25 | #- "Azure Data Explorer" 26 | #- "Azure Stack Edge" 27 | #- "Backup" 28 | #- "Batch" 29 | #- "Bot Service" 30 | #- "Cache" 31 | #- "Cognitive Services" 32 | #- "Compute" 33 | #- "Container Registry" 34 | #- "Cosmos DB" 35 | #- "Custom Provider" 36 | #- "Data Box" 37 | #- "Data Factory" 38 | #- "Data Lake" 39 | #- "Event Grid" 40 | #- "Event Hub" 41 | #- "General" 42 | #- "Guest Configuration" 43 | #- "HDInsight" 44 | #- "Internet of Things" 45 | - "Key Vault" 46 | #- "Kubernetes" 47 | #- "Kubernetes service" 48 | #- "Lighthouse" 49 | #- "Logic Apps" 50 | #- "Machine Learning" 51 | #- "Managed Application" 52 | #- "Monitoring" 53 | #- "Network" 54 | #- "Portal" 55 | #- "SQL" 56 | #- "Search" 57 | #- "Security Center" 58 | #- "Service Bus" 59 | #- "Service Fabric" 60 | #- "SignalR" 61 | #- "Storage" 62 | #- "Stream Analytics" 63 | #- "Synapse" 64 | #- "Tags" 65 | #- "VM Image Builder" 66 | 67 | 68 | # Specify Azure Policy Definition displayNames that you want to exclude from the results 69 | exclude_policies: 70 | General: 71 | - "Allow resource creation only in Asia data centers" 72 | - "Allow resource creation only in European data centers" 73 | - "Allow resource creation only in India data centers" 74 | - "Allow resource creation only in United States data centers" 75 | Tags: 76 | - "Allow resource creation if 'department' tag set" 77 | - "Allow resource creation if 'environment' tag value in allowed values" 78 | 79 | API Management: 80 | - "" 81 | 82 | API for FHIR: 83 | - "" 84 | 85 | App Configuration: 86 | - "" 87 | 88 | App Platform: 89 | - "" 90 | 91 | App Service: 92 | - "" 93 | 94 | Attestation: 95 | - "" 96 | 97 | Automanage: 98 | - "" 99 | 100 | Automation: 101 | - "" 102 | 103 | Azure Data Explorer: 104 | - "" 105 | 106 | Azure Stack Edge: 107 | - "" 108 | 109 | Backup: 110 | - "" 111 | 112 | Batch: 113 | - "" 114 | 115 | Bot Service: 116 | - "" 117 | 118 | Cache: 119 | - "" 120 | 121 | Cognitive Services: 122 | - "" 123 | 124 | Compute: 125 | - "" 126 | 127 | Container Registry: 128 | - "" 129 | 130 | Cosmos DB: 131 | - "" 132 | 133 | Custom Provider: 134 | - "" 135 | 136 | Data Box: 137 | - "" 138 | 139 | Data Factory: 140 | - "" 141 | 142 | Data Lake: 143 | - "" 144 | 145 | Event Grid: 146 | - "" 147 | 148 | Event Hub: 149 | - "" 150 | 151 | Guest Configuration: 152 | - "" 153 | 154 | HDInsight: 155 | - "" 156 | 157 | Internet of Things: 158 | - "" 159 | 160 | Key Vault: 161 | - "" 162 | 163 | Kubernetes: 164 | - "" 165 | 166 | Kubernetes service: 167 | - "" 168 | 169 | Lighthouse: 170 | - "" 171 | 172 | Logic Apps: 173 | - "" 174 | 175 | Machine Learning: 176 | - "" 177 | 178 | Managed Application: 179 | - "" 180 | 181 | Monitoring: 182 | - "" 183 | 184 | Network: 185 | - "" 186 | 187 | Portal: 188 | - "" 189 | 190 | SQL: 191 | - "" 192 | 193 | Search: 194 | - "" 195 | 196 | Security Center: 197 | - "" 198 | 199 | Service Bus: 200 | - "" 201 | 202 | Service Fabric: 203 | - "" 204 | 205 | SignalR: 206 | - "" 207 | 208 | Storage: 209 | - "" 210 | 211 | Stream Analytics: 212 | - "" 213 | 214 | Synapse: 215 | - "" 216 | 217 | VM Image Builder: 218 | - "" 219 | -------------------------------------------------------------------------------- /test/files/no_params.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | name_no_params = "example_NP_Enforce" 3 | subscription_name_no_params = "example" 4 | management_group_no_params = "" 5 | enforcement_mode_no_params = true 6 | policy_ids_no_params = [ 7 | # ----------------------------------------------------------------------------------------------------------------- 8 | # Compute 9 | # ----------------------------------------------------------------------------------------------------------------- 10 | "06a78e20-9358-41c9-923c-fb736d382a4d", # Audit VMs that do not use managed disks 11 | "0015ea4d-51ff-4ce3-8d8c-f3f8f0179a56", # Audit virtual machines without disaster recovery configured 12 | "465f0161-0087-490a-9ad9-ad6217f4f43a", # Require automatic OS image patching on Virtual Machine Scale Sets 13 | 14 | # ----------------------------------------------------------------------------------------------------------------- 15 | # Data Lake 16 | # ----------------------------------------------------------------------------------------------------------------- 17 | "a7ff3161-0087-490a-9ad9-ad6217f4f43a", # Require encryption on Data Lake Store accounts 18 | 19 | # ----------------------------------------------------------------------------------------------------------------- 20 | # General 21 | # ----------------------------------------------------------------------------------------------------------------- 22 | "0a914e76-4921-4c19-b460-a2d36003525a", # Audit resource location matches resource group location 23 | 24 | # ----------------------------------------------------------------------------------------------------------------- 25 | # Network 26 | # ----------------------------------------------------------------------------------------------------------------- 27 | "35f9c03a-cc27-418e-9c0c-539ff999d010", # Gateway subnets should not be configured with a network security group 28 | "88c0b9da-ce96-4b03-9635-f29a937e2900", # Network interfaces should disable IP forwarding 29 | "83a86a26-fd1f-447c-b59d-e51f44264114", # Network interfaces should not have public IPs 30 | 31 | ] 32 | } 33 | 34 | # --------------------------------------------------------------------------------------------------------------------- 35 | # Azure Policy name lookups: 36 | # Because the policies are built-in, we can just look up their IDs by their names. 37 | # --------------------------------------------------------------------------------------------------------------------- 38 | data "azurerm_policy_definition" "no_params" { 39 | count = length(local.policy_ids_no_params) 40 | name = element(local.policy_ids_no_params, count.index) 41 | } 42 | 43 | locals { 44 | no_params_policy_definitions = flatten([tolist([ 45 | for definition in data.azurerm_policy_definition.no_params.*.id : 46 | map("policyDefinitionId", definition) 47 | ]) 48 | ]) 49 | } 50 | 51 | # --------------------------------------------------------------------------------------------------------------------- 52 | # Conditional data lookups: If the user supplies management group, look up the ID of the management group 53 | # --------------------------------------------------------------------------------------------------------------------- 54 | data "azurerm_management_group" "no_params" { 55 | count = local.management_group_no_params != "" ? 1 : 0 56 | display_name = local.management_group_no_params 57 | } 58 | 59 | ### If the user supplies subscription, look up the ID of the subscription 60 | data "azurerm_subscriptions" "no_params" { 61 | count = local.subscription_name_no_params != "" ? 1 : 0 62 | display_name_contains = local.subscription_name_no_params 63 | } 64 | 65 | locals { 66 | no_params_scope = local.management_group_no_params != "" ? data.azurerm_management_group.no_params[0].id : element(data.azurerm_subscriptions.no_params[0].subscriptions.*.id, 0) 67 | } 68 | 69 | # --------------------------------------------------------------------------------------------------------------------- 70 | # Policy Initiative 71 | # --------------------------------------------------------------------------------------------------------------------- 72 | resource "azurerm_policy_set_definition" "no_params" { 73 | name = local.name_no_params 74 | policy_type = "Custom" 75 | display_name = local.name_no_params 76 | description = local.name_no_params 77 | management_group_name = local.management_group_no_params == "" ? null : local.management_group_no_params 78 | policy_definitions = tostring(jsonencode(local.no_params_policy_definitions)) 79 | metadata = tostring(jsonencode({ 80 | category = local.name_no_params 81 | })) 82 | } 83 | 84 | # --------------------------------------------------------------------------------------------------------------------- 85 | # Azure Policy Assignments 86 | # Apply the Policy Initiative to the specified scope 87 | # --------------------------------------------------------------------------------------------------------------------- 88 | resource "azurerm_policy_assignment" "no_params" { 89 | name = local.name_no_params 90 | policy_definition_id = azurerm_policy_set_definition.no_params.id 91 | scope = local.no_params_scope 92 | enforcement_mode = local.enforcement_mode_no_params 93 | } 94 | 95 | # --------------------------------------------------------------------------------------------------------------------- 96 | # Outputs 97 | # --------------------------------------------------------------------------------------------------------------------- 98 | output "no_params_policy_assignment_ids" { 99 | value = azurerm_policy_assignment.no_params.id 100 | description = "The IDs of the Policy Assignments." 101 | } 102 | 103 | output "no_params_scope" { 104 | value = local.no_params_scope 105 | description = "The target scope - either the management group or subscription, depending on which parameters were supplied" 106 | } 107 | 108 | output "no_params_policy_set_definition_id" { 109 | value = azurerm_policy_set_definition.no_params.id 110 | description = "The ID of the Policy Set Definition." 111 | } 112 | 113 | output "no_params_count_of_policies_applied" { 114 | description = "The number of Policies applied as part of the Policy Initiative" 115 | value = length(local.policy_ids_no_params) 116 | } 117 | -------------------------------------------------------------------------------- /test/files/stash/test_services_statistics.py: -------------------------------------------------------------------------------- 1 | # import unittest 2 | # from cloud_guardrails.shared import utils 3 | # from cloud_guardrails.iam_definition.services import Services 4 | # import yaml 5 | # import ruamel.yaml 6 | # import json 7 | # import os 8 | # 9 | # example_config_file = os.path.abspath(os.path.join( 10 | # os.path.dirname(__file__), 11 | # "service-parameters-template.yml" 12 | # )) 13 | # 14 | # 15 | # class ServiceStatisticsTestCase(unittest.TestCase): 16 | # def setUp(self): 17 | # # self.policy_json = utils.get_policy_json(service_name="Automation", filename="Automation_AuditUnencryptedVars_Audit.json") 18 | # self.services = Services() 19 | # 20 | # def test_services(self): 21 | # services = Services() 22 | # # print(f"Service Names: {', '.join(services.service_names)}") 23 | # 24 | # results = services.display_names_no_params 25 | # print(f"No parameters or modification: {len(results)}") 26 | # self.assertTrue(len(results) >= 275) 27 | # 28 | # results = services.display_names_params_optional 29 | # print(f"Params Optional: {len(results)}") 30 | # self.assertTrue(len(results) >= 34) 31 | # 32 | # results = services.display_names_params_required 33 | # print(f"Params Required: {len(results)}") 34 | # self.assertTrue(len(results) >= 77) 35 | -------------------------------------------------------------------------------- /test/files/stash/test_terraform.py: -------------------------------------------------------------------------------- 1 | # import unittest 2 | # import json 3 | # from cloud_guardrails.shared import utils 4 | # from cloud_guardrails.terraform.terraform import TerraformTemplateNoParams 5 | # from cloud_guardrails.iam_definition.services import Services, Service 6 | # 7 | # 8 | # class TerraformTemplateNoParamsTestCase(unittest.TestCase): 9 | # def test_terraform_single_service(self): 10 | # service = Services(service_names=["Key Vault"]) 11 | # policy_names = service.get_display_names_sorted_by_service_no_params() 12 | # subscription_name = "example" 13 | # management_group = "" 14 | # enforcement_mode = False 15 | # terraform_template = TerraformTemplateNoParams(policy_names=policy_names, subscription_name=subscription_name, 16 | # management_group=management_group, 17 | # enforcement_mode=enforcement_mode) 18 | # result = terraform_template.rendered() 19 | # print(result) 20 | # self.assertListEqual(list(terraform_template.policy_names.keys()), ["Key Vault"]) 21 | # self.assertTrue("Key vaults should have soft delete enabled" in terraform_template.policy_names.get("Key Vault")) 22 | # self.assertTrue("example_noparams" in result) 23 | # 24 | # def test_terraform_all_services(self): 25 | # services = Services() 26 | # subscription_name = "example" 27 | # management_group = "" 28 | # enforcement_mode = False 29 | # policy_names = services.get_display_names_sorted_by_service_no_params() 30 | # terraform_template = TerraformTemplateNoParams(policy_names=policy_names, subscription_name=subscription_name, 31 | # management_group=management_group, 32 | # enforcement_mode=enforcement_mode) 33 | # result = terraform_template.rendered() 34 | # policy_name_keys = list(terraform_template.policy_names.keys()) 35 | # all_services = utils.get_service_names() 36 | # print(f"Length of Policy name keys: {len(policy_name_keys)}") 37 | # print(f"Length of All Services list: {len(all_services)}") 38 | # self.assertTrue(len(policy_name_keys) >= 39) 39 | # for service in policy_name_keys: 40 | # self.assertTrue(service in all_services) 41 | # # print(result) 42 | # 43 | -------------------------------------------------------------------------------- /test/iam_definition/test_parameter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import json 4 | from cloud_guardrails.iam_definition.parameter import Parameter 5 | 6 | # Params Required 7 | policy_definition_file = os.path.abspath(os.path.join( 8 | os.path.dirname(__file__), 9 | os.pardir, 10 | "files", 11 | "AllowedUsersGroups.json" 12 | )) 13 | with open(policy_definition_file) as json_file: 14 | params_required_definition = json.load(json_file) 15 | 16 | 17 | class ParameterTestCase(unittest.TestCase): 18 | def setUp(self): 19 | """Use 'Kubernetes cluster pods and containers should only run with approved user and group IDs'""" 20 | parameters = params_required_definition.get("properties").get("parameters") 21 | self.array_parameter = Parameter(name="excludedNamespaces", parameter_json=parameters.get("excludedNamespaces")) 22 | 23 | def test_parameter_attributes(self): 24 | print("Testing Parameter attributes for Array type") 25 | self.assertEqual(self.array_parameter.type, "Array") 26 | self.assertEqual(self.array_parameter.display_name, "Namespace exclusions") 27 | self.assertEqual(self.array_parameter.description, "List of Kubernetes namespaces to exclude from policy evaluation.") 28 | self.assertListEqual(self.array_parameter.default_value, ["kube-system", "gatekeeper-system", "azure-arc"]) 29 | self.assertListEqual(list(self.array_parameter.metadata_json.keys()), ["displayName", "description"]) 30 | print(json.dumps(self.array_parameter.json(), indent=4)) 31 | expected_result = { 32 | "name": "excludedNamespaces", 33 | "type": "Array", 34 | "description": "List of Kubernetes namespaces to exclude from policy evaluation.", 35 | "display_name": "Namespace exclusions", 36 | "default_value": [ 37 | "kube-system", 38 | "gatekeeper-system", 39 | "azure-arc" 40 | ], 41 | "value": [ 42 | "kube-system", 43 | "gatekeeper-system", 44 | "azure-arc" 45 | ] 46 | } 47 | self.assertDictEqual(self.array_parameter.json(), expected_result) 48 | 49 | def test_parameters_parameter_config(self): 50 | results = self.array_parameter.parameter_config() 51 | print(json.dumps(results, indent=4)) 52 | expected_results = { 53 | "excludedNamespaces": { 54 | "default_value": [ 55 | "kube-system", 56 | "gatekeeper-system", 57 | "azure-arc" 58 | ], 59 | "allowed_values": None, 60 | "type": "Array" 61 | } 62 | } 63 | self.assertDictEqual(results, expected_results) 64 | -------------------------------------------------------------------------------- /test/iam_definition/test_properties.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import unittest 4 | from cloud_guardrails.iam_definition.properties import Properties 5 | # Params Required 6 | policy_definition_file = os.path.abspath(os.path.join( 7 | os.path.dirname(__file__), 8 | os.pardir, 9 | "files", 10 | "AllowedUsersGroups.json" 11 | )) 12 | with open(policy_definition_file) as json_file: 13 | params_required_definition = json.load(json_file) 14 | 15 | # Preview definition without the '[Preview]: ' Prefix 16 | policy_definition_file = os.path.abspath(os.path.join( 17 | os.path.dirname(__file__), 18 | os.pardir, 19 | "files", 20 | "AzureMonitoring_AddSystemIdentity_Prerequisite.json" 21 | )) 22 | with open(policy_definition_file) as json_file: 23 | weird_preview_definition = json.load(json_file) 24 | 25 | 26 | class PropertiesTestCase(unittest.TestCase): 27 | def setUp(self): 28 | """Use 'Kubernetes cluster pods and containers should only run with approved user and group IDs'""" 29 | self.properties = Properties(properties_json=params_required_definition.get("properties")) 30 | self.weird_preview_definition_properties = Properties(properties_json=weird_preview_definition.get("properties")) 31 | 32 | def test_properties_attributes(self): 33 | self.assertEqual(self.properties.display_name, "Kubernetes cluster pods and containers should only run with approved user and group IDs") 34 | self.assertEqual(self.properties.policy_type, "BuiltIn") 35 | self.assertEqual(self.properties.mode, "Microsoft.Kubernetes.Data") 36 | self.assertTrue(self.properties.description.startswith("This policy controls the user,")) 37 | self.assertEqual(self.properties.version, "2.0.1") 38 | self.assertEqual(self.properties.category, "Kubernetes") 39 | self.assertEqual(self.properties.preview, None) 40 | self.assertEqual(self.properties.deprecated, None) 41 | self.assertListEqual(list(self.properties.policy_rule.keys()), ["if", "then"]) 42 | # print(self.properties.parameter_names) 43 | 44 | def test_parameter_names(self): 45 | # results = list(self.properties.parameters.keys()) 46 | results = self.properties.parameter_names 47 | expected_results = [ 48 | "effect", 49 | "excludedNamespaces", 50 | "namespaces", 51 | "runAsUserRule", 52 | "runAsUserRanges", 53 | "runAsGroupRule", 54 | "runAsGroupRanges", 55 | "supplementalGroupsRule", 56 | "supplementalGroupsRanges", 57 | "fsGroupRule", 58 | "fsGroupRanges" 59 | ] 60 | print(json.dumps(results, indent=4)) 61 | self.assertListEqual(results, expected_results) 62 | 63 | def test_preview_definition_name(self): 64 | print(self.weird_preview_definition_properties.preview) 65 | print(self.weird_preview_definition_properties.display_name) 66 | self.assertTrue('[Preview]: ' not in self.weird_preview_definition_properties.display_name) 67 | -------------------------------------------------------------------------------- /test/shared/test_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import yaml 4 | import json 5 | from yaml.constructor import ConstructorError 6 | import logging 7 | from cloud_guardrails.shared import utils 8 | from cloud_guardrails.shared.config import get_config_template, Config, get_config_from_file, get_default_config 9 | 10 | logger = logging.getLogger(__name__) 11 | example_config_file = os.path.abspath(os.path.join( 12 | os.path.dirname(__file__), 13 | os.path.pardir, 14 | "files", 15 | "example-config.yml" 16 | )) 17 | 18 | 19 | class ConfigTestCase(unittest.TestCase): 20 | def test_default_config_template(self): 21 | result = get_config_template() 22 | # print(result) 23 | 24 | def test_config_object(self): 25 | exclude_policies = {} 26 | match_only_keywords = [] 27 | exclude_services = [] 28 | config = Config(exclude_policies=exclude_policies, match_only_keywords=match_only_keywords, exclude_services=exclude_services) 29 | # print(config.__str__()) 30 | self.assertTrue(config.__str__() == '{"match_only_keywords": [], "exclude_services": [], "exclude_policies": {}}') 31 | 32 | def test_config_methods(self): 33 | exclude_policies = { 34 | "General": [ 35 | "Allow resource creation only in Asia data centers" 36 | ] 37 | } 38 | match_only_keywords = [] 39 | exclude_services = ["Key Vault"] 40 | config = Config(exclude_policies=exclude_policies, match_only_keywords=match_only_keywords, exclude_services=exclude_services) 41 | # Case: Is service name excluded 42 | self.assertTrue(config.is_excluded("Key Vault", "blah")) 43 | # Case: Match only keywords 44 | self.assertFalse(config.is_excluded("Automation", "Encrypt stuff")) 45 | self.assertFalse(config.is_excluded("Automation", "do encrypt")) 46 | # Case: Exclude Policies 47 | self.assertTrue(config.is_excluded("General", "Allow resource creation only in Asia data centers")) 48 | self.assertFalse(config.is_excluded("General", "Allow resource creation only in India data centers")) 49 | 50 | def test_config_when_match_keyword_is_used(self): 51 | exclude_policies = { 52 | "General": [ 53 | "Allow resource creation only in Asia data centers" 54 | ] 55 | } 56 | match_only_keywords = ["Encrypt"] 57 | exclude_services = ["Key Vault"] 58 | config = Config(exclude_policies=exclude_policies, match_only_keywords=match_only_keywords, exclude_services=exclude_services) 59 | # Case: Is service name excluded 60 | self.assertTrue(config.is_excluded("Key Vault", "blah")) 61 | # Case: Match only keywords 62 | self.assertFalse(config.is_excluded("Automation", "Encrypt stuff")) 63 | 64 | # These ones will all fail because they don't explicitly match the required keywords 65 | self.assertTrue(config.is_excluded("Automation", "HotDogsAreSandwiches")) 66 | self.assertTrue(config.is_excluded("General", "Allow resource creation only in Asia data centers")) 67 | self.assertTrue(config.is_excluded("General", "Allow resource creation only in India data centers")) 68 | 69 | def test_read_config_file(self): 70 | # print(example_config_file) 71 | config = get_config_from_file(example_config_file) 72 | # print(config.__str__()) 73 | # print(json.dumps(config.json(), indent=4)) 74 | keys = list(config.json().keys()) 75 | self.assertListEqual(keys, ['match_only_keywords', 'exclude_services', 'exclude_policies']) 76 | 77 | def test_read_config(self): 78 | default_config_template = get_config_template() 79 | # print(default_config_template) 80 | 81 | def test_default_config(self): 82 | config = get_default_config() 83 | # print(config) 84 | 85 | def test_exclude_keywords_config(self): 86 | config = get_default_config(exclude_keywords=["private link"]) 87 | self.assertTrue(config.is_excluded("API for FHIR", "Azure API for FHIR should use private link")) 88 | self.assertTrue(config.is_excluded("App Configuration", "App Configuration should use private link")) 89 | self.assertTrue(config.is_excluded("Cosmos DB", "CosmosDB accounts should use private link")) 90 | 91 | def test_exclude_services_config(self): 92 | config = get_default_config(exclude_services=["Guest Configuration"]) 93 | response = config.is_service_excluded(service_name="Guest Configuration") 94 | print(response) 95 | self.assertTrue(response) 96 | 97 | 98 | def test_gh_44_bad_config_file(self): 99 | bad_config_file = os.path.abspath(os.path.join( 100 | os.path.dirname(__file__), 101 | os.path.pardir, 102 | "files", 103 | "bad-config.yml" 104 | )) 105 | with self.assertRaises(yaml.constructor.ConstructorError): 106 | config = get_config_from_file(bad_config_file) 107 | -------------------------------------------------------------------------------- /test/shared/test_parameters_categorized.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import json 4 | from cloud_guardrails.shared.parameters_categorized import CategorizedParameters 5 | from cloud_guardrails.shared import utils 6 | from cloud_guardrails.iam_definition.azure_policies import AzurePolicies 7 | 8 | parameters_config_file = os.path.abspath(os.path.join( 9 | os.path.dirname(__file__), 10 | os.path.pardir, 11 | os.path.pardir, 12 | "examples", 13 | "parameters-config-example.yml" 14 | )) 15 | parameters_config = utils.read_yaml_file(parameters_config_file) 16 | 17 | 18 | class ParametersCategorizedTestCase(unittest.TestCase): 19 | def setUp(self) -> None: 20 | azure_policies = AzurePolicies(service_names=["Batch"]) 21 | self.policy_ids_sorted_by_service = azure_policies.get_all_policy_ids_sorted_by_service( 22 | no_params=False, params_optional=True, params_required=True, 23 | audit_only=False) 24 | self.categorized_parameters = CategorizedParameters( 25 | azure_policies=azure_policies, 26 | parameters_config=parameters_config, 27 | params_required=True, 28 | params_optional=True, 29 | audit_only=False 30 | ) 31 | 32 | def test_validate_parameters_config(self): 33 | results = self.categorized_parameters.parameters_config 34 | print(json.dumps(results, indent=4)) 35 | expected_results = { 36 | "API Management": { 37 | "API Management service should use a SKU that supports virtual networks": { 38 | "effect": "Deny", 39 | "listOfAllowedSKUs": [ 40 | "Developer", 41 | "Premium", 42 | "Isolated" 43 | ] 44 | } 45 | }, 46 | "Kubernetes": { 47 | "Kubernetes cluster containers CPU and memory resource limits should not exceed the specified limits": { 48 | "effect": "Audit", 49 | "excludedNamespaces": [ 50 | "kube-system", 51 | "gatekeeper-system", 52 | "azure-arc" 53 | ], 54 | "namespaces": [], 55 | "labelSelector": {}, 56 | "cpuLimit": "200m", 57 | "memoryLimit": "1Gi" 58 | }, 59 | "Kubernetes cluster containers should not share host process ID or host IPC namespace": { 60 | "effect": "Audit", 61 | "excludedNamespaces": [ 62 | "kube-system", 63 | "gatekeeper-system", 64 | "azure-arc" 65 | ], 66 | "namespaces": [], 67 | "labelSelector": {} 68 | }, 69 | "Kubernetes cluster containers should not use forbidden sysctl interfaces": { 70 | "effect": "Audit", 71 | "excludedNamespaces": [ 72 | "kube-system", 73 | "gatekeeper-system", 74 | "azure-arc" 75 | ], 76 | "namespaces": [], 77 | "labelSelector": {}, 78 | "forbiddenSysctls": [] 79 | } 80 | } 81 | } 82 | self.assertDictEqual(results, expected_results) 83 | 84 | def test_parameter_config_output(self): 85 | results = self.categorized_parameters.service_categorized_parameters 86 | print(json.dumps(results, indent=4)) 87 | -------------------------------------------------------------------------------- /test/templates/test_parameters_template.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | from cloud_guardrails.shared.config import get_empty_config, get_default_config 4 | from cloud_guardrails.templates.parameters_template import ParameterTemplate, ParameterSegment 5 | from cloud_guardrails.shared import utils 6 | 7 | 8 | class ParameterTemplateTestCase(unittest.TestCase): 9 | def setUp(self) -> None: 10 | self.config = get_default_config( 11 | match_only_keywords=["Metric alert rules should be configured on Batch accounts"] 12 | ) 13 | self.config_enforce = get_default_config( 14 | match_only_keywords=["Storage account public access should be disallowed"] 15 | ) 16 | self.parameter_template_audit = ParameterTemplate(config=self.config, params_optional=False, params_required=True, enforce=False) 17 | self.parameter_template_enforce = ParameterTemplate(config=self.config_enforce, params_optional=True, params_required=False, enforce=True) 18 | self.parameter_segment = ParameterSegment(parameter_name="effect", parameter_type="string", allowed_values=["AuditIfNotExists", "Disabled"], default_value="AuditIfNotExists", value="AuditIfNotExists") 19 | 20 | def test_parameter_segment(self): 21 | print(self.parameter_segment) 22 | 23 | def test_set_parameter_config_case_1_audit(self): 24 | results = self.parameter_template_audit.json() 25 | print(json.dumps(results, indent=4)) 26 | expected_results = { 27 | "Batch": { 28 | "Metric alert rules should be configured on Batch accounts": [ 29 | { 30 | "name": "effect", 31 | "type": "string", 32 | "allowed_values": [ 33 | "AuditIfNotExists", 34 | "Disabled" 35 | ], 36 | "default_value": "AuditIfNotExists", 37 | "value": "AuditIfNotExists" 38 | }, 39 | { 40 | "name": "metricName", 41 | "type": "String", 42 | "allowed_values": None, 43 | "default_value": None, 44 | "value": None 45 | } 46 | ] 47 | } 48 | } 49 | self.assertDictEqual(results, expected_results) 50 | 51 | def test_set_parameter_config_case_2_enforce(self): 52 | results = self.parameter_template_enforce.json() 53 | print(json.dumps(results, indent=4)) 54 | expected_results = { 55 | "Storage": { 56 | "Storage account public access should be disallowed": [ 57 | { 58 | "name": "effect", 59 | "type": "string", 60 | "allowed_values": [ 61 | "audit", 62 | "deny", 63 | "disabled" 64 | ], 65 | "default_value": "audit", 66 | "value": "deny" 67 | } 68 | ] 69 | } 70 | } 71 | default_value = results["Storage"]["Storage account public access should be disallowed"][0]["default_value"] 72 | value = results["Storage"]["Storage account public access should be disallowed"][0]["value"] 73 | 74 | self.assertEqual(default_value, "audit") 75 | self.assertEqual(value, "deny") 76 | # self.assertDictEqual(results, expected_results) 77 | 78 | def test_parameters_template_rendered_case_1_audit(self): 79 | results = self.parameter_template_audit.rendered() 80 | print(results) 81 | expected_results = """# --------------------------------------------------------------------------------------------------------------------- 82 | # Batch 83 | # --------------------------------------------------------------------------------------------------------------------- 84 | Batch: 85 | "Metric alert rules should be configured on Batch accounts": 86 | effect: AuditIfNotExists # Allowed: ["AuditIfNotExists", "Disabled"] 87 | metricName: # Note: No default parameters 88 | """ 89 | self.assertEqual(results, expected_results) 90 | 91 | def test_parameters_template_rendered_case_2_enforce(self): 92 | results = self.parameter_template_enforce.rendered() 93 | print(results) 94 | expected_results = """# --------------------------------------------------------------------------------------------------------------------- 95 | # Storage 96 | # --------------------------------------------------------------------------------------------------------------------- 97 | Storage: 98 | "Storage account public access should be disallowed": 99 | effect: deny # Allowed: ["audit", "deny", "disabled"] 100 | """ 101 | self.assertEqual(results, expected_results) 102 | -------------------------------------------------------------------------------- /test/terraform/test_python_hcl2.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | import os 4 | import hcl2 5 | from cloud_guardrails.shared import utils 6 | from cloud_guardrails.iam_definition.azure_policies import AzurePolicies 7 | 8 | 9 | class HclParserTestCase(unittest.TestCase): 10 | def setUp(self) -> None: 11 | no_params_file = os.path.abspath(os.path.join( 12 | os.path.dirname(__file__), 13 | os.path.pardir, 14 | "files", 15 | "no_params.tf" 16 | )) 17 | with open(no_params_file, 'r') as file: 18 | self.no_params = hcl2.load(file) 19 | 20 | params_optional_file = os.path.abspath(os.path.join( 21 | os.path.dirname(__file__), 22 | os.path.pardir, 23 | "files", 24 | "params_optional.tf" 25 | )) 26 | with open(params_optional_file, 'r') as file: 27 | self.params_optional = hcl2.load(file) 28 | 29 | params_required_file = os.path.abspath(os.path.join( 30 | os.path.dirname(__file__), 31 | os.path.pardir, 32 | "files", 33 | "params_required.tf" 34 | )) 35 | with open(params_required_file, 'r') as file: 36 | self.params_required = hcl2.load(file) 37 | 38 | self.azure_policies = AzurePolicies() 39 | 40 | def test_hcl_no_params(self): 41 | policy_ids = self.no_params.get("locals")[0].get("policy_ids_no_params")[0] 42 | # There should be no parameters for any of the policy definitions with these IDs 43 | for policy_id in policy_ids: 44 | parameters = self.azure_policies.get_parameters_by_policy_id(policy_id=policy_id) 45 | # print(parameters) 46 | # Note for #92: Here is where we can find the ones that don't have any parameters 47 | # print(json.dumps(parameters, indent=4)) 48 | self.assertDictEqual(parameters, {}) 49 | 50 | def test_hcl_params_optional(self): 51 | policy_ids = self.params_optional['locals'][0]['policy_ids_example_PO_Audit'][0] 52 | for policy_id in policy_ids: 53 | optional_parameters = self.azure_policies.get_optional_parameters(policy_id=policy_id) 54 | # print(optional_parameters) 55 | # The list of optional parameters should not be empty 56 | self.assertTrue(optional_parameters) 57 | # there should be no required parameters, so the list should be empty 58 | required_parameters = self.azure_policies.get_required_parameters(policy_id=policy_id) 59 | self.assertListEqual(required_parameters, []) 60 | 61 | def test_hcl_params_required(self): 62 | policy_ids = self.params_required['locals'][0]['policy_ids_example_PR_Audit'][0] 63 | for policy_id in policy_ids: 64 | required_parameters = self.azure_policies.get_required_parameters(policy_id=policy_id) 65 | # The list of required parameters should not be empty 66 | self.assertTrue(required_parameters) 67 | 68 | # the list of optional parameters should be empty 69 | optional_parameters = self.azure_policies.get_optional_parameters(policy_id=policy_id) 70 | self.assertListEqual(optional_parameters, []) 71 | # print(optional_parameters) 72 | 73 | -------------------------------------------------------------------------------- /test/terraform/test_terraform_guardrails.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | from cloud_guardrails.shared.config import get_default_config 4 | from cloud_guardrails.terraform.guardrails import TerraformGuardrails 5 | 6 | 7 | class AzurePoliciesTestCase(unittest.TestCase): 8 | def setUp(self) -> None: 9 | config = get_default_config(exclude_services=[]) 10 | self.terraform = TerraformGuardrails( 11 | service="Key Vault", 12 | config=config, 13 | subscription="example", 14 | management_group="", 15 | parameters_config={}, 16 | no_params=True, 17 | params_optional=False, 18 | params_required=False, 19 | category="Testing", 20 | enforcement_mode=True, 21 | verbosity=3 22 | ) 23 | 24 | def test_policy_metadata(self): 25 | policy_id_pairs = self.terraform.policy_id_pairs() 26 | print(json.dumps(policy_id_pairs, indent=4)) 27 | policy_names = self.terraform.policy_names() 28 | print(json.dumps(policy_names, indent=4)) 29 | policy_ids = self.terraform.policy_ids() 30 | print(json.dumps(policy_ids, indent=4)) 31 | self.assertTrue(len(policy_names) == len(policy_ids)) 32 | 33 | -------------------------------------------------------------------------------- /test/terraform/test_terraform_no_params.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | from cloud_guardrails.terraform.terraform_no_params import TerraformTemplateNoParams 4 | from cloud_guardrails.iam_definition.azure_policies import AzurePolicies 5 | from cloud_guardrails.shared.config import get_default_config 6 | from cloud_guardrails.shared import utils 7 | 8 | 9 | class TerraformTemplateNoParamsTestCase(unittest.TestCase): 10 | def setUp(self) -> None: 11 | exclude_services = [] 12 | subscription = "example" 13 | management_group = "" 14 | enforcement_mode = False 15 | category = "Testing" 16 | 17 | config = get_default_config(exclude_services=exclude_services) 18 | # All Services 19 | self.azure_policies = AzurePolicies(service_names=["all"], config=config) 20 | self.policy_id_pairs = self.azure_policies.get_all_policy_ids_sorted_by_service( 21 | no_params=True 22 | ) 23 | self.terraform_template = TerraformTemplateNoParams( 24 | policy_id_pairs=self.policy_id_pairs, 25 | subscription_name=subscription, 26 | management_group=management_group, 27 | enforcement_mode=enforcement_mode, 28 | category=category 29 | ) 30 | # Key Vault only 31 | self.kv_azure_policies = AzurePolicies(service_names=["Key Vault"], config=config) 32 | kv_policy_id_pairs = self.kv_azure_policies.get_all_policy_ids_sorted_by_service( 33 | no_params=True 34 | ) 35 | import json 36 | print(json.dumps(kv_policy_id_pairs, indent=4)) 37 | self.kv_terraform_template = TerraformTemplateNoParams( 38 | policy_id_pairs=kv_policy_id_pairs, 39 | subscription_name=subscription, 40 | management_group=management_group, 41 | enforcement_mode=enforcement_mode, 42 | category=category 43 | ) 44 | 45 | def test_terraform_key_vault(self): 46 | self.assertEqual("example_NP_Audit", self.kv_terraform_template.initiative_name) 47 | print(self.terraform_template.initiative_name) 48 | self.assertEqual("no_params", self.kv_terraform_template.label) 49 | print(self.terraform_template.label) 50 | self.assertEqual("example", self.kv_terraform_template.subscription_name) 51 | self.assertEqual("", self.kv_terraform_template.management_group) 52 | policy_id_pairs = self.kv_azure_policies.get_all_policy_ids_sorted_by_service(no_params=True) 53 | # print(json.dumps(policy_id_pairs, indent=4)) 54 | kv_policy_names = list(policy_id_pairs.get("Key Vault").keys()) 55 | expected_results_file = os.path.join(os.path.dirname(__file__), os.path.pardir, "files", "policy_id_pairs_kv.json") 56 | expected_results = utils.read_json_file(expected_results_file) 57 | # print(json.dumps(expected_results, indent=4)) 58 | print(kv_policy_names) 59 | for policy_name, policy_details in expected_results["Key Vault"].items(): 60 | self.assertTrue(policy_name in kv_policy_names) 61 | 62 | def test_terraform_name_length(self): 63 | tmp_terraform_template = TerraformTemplateNoParams( 64 | policy_id_pairs=self.policy_id_pairs, 65 | subscription_name="ThisSubscriptionNameIsTooDamnLong", 66 | management_group="", 67 | enforcement_mode=False, 68 | category="Testing" 69 | ) 70 | print(tmp_terraform_template.initiative_name) 71 | print(len(tmp_terraform_template.initiative_name)) 72 | self.assertTrue(tmp_terraform_template.initiative_name == "ThisSubscriptio_NP_Audit") 73 | self.assertTrue(len(tmp_terraform_template.initiative_name) <= 24) 74 | 75 | def test_terraform_name_enforcement_enforce(self): 76 | tmp_terraform_template = TerraformTemplateNoParams( 77 | policy_id_pairs=self.policy_id_pairs, 78 | subscription_name="ThisSubscriptionNameIsTooDamnLong", 79 | management_group="", 80 | enforcement_mode=True, 81 | category="Testing" 82 | ) 83 | print(tmp_terraform_template.initiative_name) 84 | print(len(tmp_terraform_template.initiative_name)) 85 | self.assertTrue(tmp_terraform_template.initiative_name == "ThisSubscript_NP_Enforce") 86 | self.assertTrue(len(tmp_terraform_template.initiative_name) <= 24) 87 | -------------------------------------------------------------------------------- /test/terraform/test_terraform_with_params.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import json 4 | from cloud_guardrails.terraform.terraform_with_params import TerraformTemplateWithParams, get_placeholder_value_given_type 5 | from cloud_guardrails.shared.parameters_categorized import CategorizedParameters 6 | from cloud_guardrails.iam_definition.azure_policies import AzurePolicies 7 | from cloud_guardrails.shared.config import get_default_config 8 | 9 | example_config_file = os.path.abspath(os.path.join( 10 | os.path.dirname(__file__), 11 | os.path.pardir, 12 | os.path.pardir, 13 | "examples", 14 | "parameters-config-example.yml" 15 | )) 16 | config = get_default_config(exclude_services=None) 17 | 18 | 19 | class TerraformTemplateWithParamsV5TestCase(unittest.TestCase): 20 | def setUp(self) -> None: 21 | azure_policies = AzurePolicies(service_names=["Kubernetes"], config=config) 22 | policy_ids_sorted_by_service = azure_policies.get_all_policy_ids_sorted_by_service( 23 | no_params=False, params_optional=True, params_required=True, 24 | audit_only=False) 25 | parameter_requirement_str = "PR" 26 | categorized_parameters = CategorizedParameters( 27 | azure_policies=azure_policies, 28 | params_required=True, 29 | params_optional=True, 30 | audit_only=False 31 | ) 32 | 33 | self.terraform_template_with_params = TerraformTemplateWithParams( 34 | policy_id_pairs=policy_ids_sorted_by_service, 35 | parameter_requirement_str=parameter_requirement_str, 36 | categorized_parameters=categorized_parameters, 37 | subscription_name="example", 38 | management_group="", 39 | enforcement_mode=False, 40 | category="Testing" 41 | ) 42 | 43 | def test_template_contents_json(self): 44 | results = self.terraform_template_with_params.template_contents_json 45 | print(json.dumps(results, indent=4)) 46 | 47 | def test_template_contents_rendered(self): 48 | results = self.terraform_template_with_params.rendered() 49 | # print(results) 50 | 51 | def test_get_placeholder_value_given_type(self): 52 | results = get_placeholder_value_given_type("array") 53 | self.assertListEqual(results, []) 54 | self.assertEqual(get_placeholder_value_given_type("string"), '""') 55 | self.assertDictEqual(get_placeholder_value_given_type("object"), {}) 56 | -------------------------------------------------------------------------------- /update_compliance_data.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # Copyright (c) 2021, salesforce.com, inc. 3 | # All rights reserved. 4 | # Licensed under the BSD 3-Clause license. 5 | # For full license text, see the LICENSE file in the repo root 6 | # or https://opensource.org/licenses/BSD-3-Clause 7 | import os 8 | import json 9 | import csv 10 | import logging 11 | import click 12 | import pandas as pd 13 | from cloud_guardrails.scrapers.azure_docs import get_azure_html 14 | from cloud_guardrails.scrapers.standard import scrape_standard 15 | from cloud_guardrails.scrapers.compliance_data import ComplianceResultsTransformer 16 | 17 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 18 | 19 | 20 | @click.command( 21 | short_help='Update the Azure compliance data.' 22 | ) 23 | @click.option( 24 | '--dest', 25 | "-d", 26 | "destination", 27 | required=True, 28 | type=click.Path(exists=True), 29 | help='Destination folder to store the docs' 30 | ) 31 | @click.option( 32 | "--download", 33 | is_flag=True, 34 | default=False, 35 | help="Download the compliance files again, potentially overwriting the ones that already exist.", 36 | ) 37 | def update_compliance_data(destination, download): 38 | links = { 39 | "cis_benchmark": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/cis-azure-1-3-0", 40 | "azure_security_benchmark": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/azure-security-benchmark", 41 | "ccmc-l3": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/cmmc-l3", 42 | "hipaa-hitrust-9-2": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/hipaa-hitrust-9-2", 43 | "iso-27007": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/iso-27001", 44 | "new-zealand-ism": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/new-zealand-ism", 45 | "nist-sp-800-53-r4": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/nist-sp-800-53-r4", 46 | "nist-sp-800-171-r2": "https://docs.microsoft.com/en-us/azure/governance/policy/samples/nist-sp-800-171-r2", 47 | } 48 | files = [] 49 | destination = os.path.abspath(destination) 50 | print(destination) 51 | # Get the file names 52 | for standard, link in links.items(): 53 | filename = os.path.join(destination, f"{standard}.html") 54 | files.append(filename) 55 | 56 | # Download the docs 57 | if download: 58 | for standard, link in links.items(): 59 | file = get_azure_html(link, os.path.join(destination, f"{standard}.html")) 60 | else: 61 | print("Download not selected; files must already exist.") 62 | 63 | print(files) 64 | results = [] 65 | 66 | cis_result = scrape_standard(files[0], replacement_string="CIS Azure", benchmark_name="CIS") 67 | results.extend(cis_result) 68 | azure_benchmark_results = scrape_standard(files[1], replacement_string="Azure Security Benchmark", benchmark_name="Azure Security Benchmark") 69 | results.extend(azure_benchmark_results) 70 | cmmc_l3_results = scrape_standard(files[2], replacement_string="CMMC L3", benchmark_name="CMMC L3") 71 | results.extend(cmmc_l3_results) 72 | hipaa_hitrust_results = scrape_standard(files[3], replacement_string="", benchmark_name="HIPAA HITRUST 9.2") 73 | results.extend(hipaa_hitrust_results) 74 | iso_results = scrape_standard(files[4], replacement_string="ISO 27001:2013", benchmark_name="ISO 27001") 75 | results.extend(iso_results) 76 | new_zealand_results = scrape_standard(files[5], replacement_string="NZISM Security Benchmark", benchmark_name="NZISM Security Benchmark") 77 | results.extend(new_zealand_results) 78 | nist_800_53_results = scrape_standard(files[6], replacement_string="NIST SP 800-53 R4", benchmark_name="NIST SP 800-53 R4") 79 | results.extend(nist_800_53_results) 80 | nist_800_171_results = scrape_standard(files[7], replacement_string="NIST SP 800-171 R2", benchmark_name="NIST SP 800-171 R2") 81 | results.extend(nist_800_171_results) 82 | 83 | write_spreadsheets(results=results, results_path=destination) 84 | compliance_results = ComplianceResultsTransformer(results_list=results) 85 | raw_json_results_path = os.path.join(destination, "compliance-data.json") 86 | with open(raw_json_results_path, "w") as file: 87 | json.dump(compliance_results.json(), file, indent=4, sort_keys=True) 88 | print(f"Saved json results to {raw_json_results_path}") 89 | 90 | 91 | def write_spreadsheets(results: list, results_path: str): 92 | field_names = [ 93 | "benchmark", 94 | "category", 95 | "requirement", 96 | "requirement_id", 97 | "service_name", 98 | "name", 99 | "policy_id", 100 | "description", 101 | "effects", 102 | "github_link", 103 | "github_version", 104 | "id" 105 | ] 106 | csv_file_path = os.path.join(results_path, "compliance-data.csv") 107 | with open(csv_file_path, 'w', newline='') as csv_file: 108 | writer = csv.DictWriter(csv_file, fieldnames=field_names) 109 | writer.writeheader() 110 | for row in results: 111 | writer.writerow(row) 112 | print(f"CSV updated! Wrote {len(results)} rows. Path: {csv_file_path}") 113 | 114 | df_new = pd.read_csv(csv_file_path) 115 | excel_file_path = os.path.join(results_path, "compliance-data.xlsx") 116 | writer = pd.ExcelWriter(excel_file_path) 117 | df_new.to_excel(writer, index=False) 118 | writer.save() 119 | print(f"Excel file updated! Wrote {len(results)} rows. Path: {excel_file_path}") 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /update_iam_definition.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, salesforce.com, inc. 2 | # All rights reserved. 3 | # Licensed under the BSD 3-Clause license. 4 | # For full license text, see the LICENSE file in the repo root 5 | # or https://opensource.org/licenses/BSD-3-Clause 6 | import click 7 | import os 8 | import json 9 | from cloud_guardrails.shared import utils 10 | from cloud_guardrails.scrapers.parse_builtin_definitions import create_azure_builtin_definition 11 | 12 | 13 | @click.command( 14 | short_help='Update the single file containing data on all the Azure Policy Definitions.' 15 | ) 16 | def update_iam_definition(): 17 | results = create_azure_builtin_definition() 18 | results_path = os.path.join(utils.DATA_FILE_DIRECTORY, "iam-definition.json") 19 | if os.path.exists(results_path): 20 | print("File already exists; removing") 21 | os.remove(results_path) 22 | with open(results_path, "w") as file: 23 | json.dump(results, file, indent=4) 24 | print(f"Wrote the new IAM definition to: {results_path}") 25 | 26 | 27 | if __name__ == '__main__': 28 | update_iam_definition() 29 | -------------------------------------------------------------------------------- /utils/combine_csvs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 3 | import glob 4 | import pandas as pd 5 | 6 | 7 | def combine_csvs(): 8 | csv_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, "docs", "summaries")) 9 | file = os.path.join(csv_folder, "all_policies.csv") 10 | os.chdir(csv_folder) 11 | extension = 'csv' 12 | # Remove the existing file first 13 | # file = "all_policies.csv" 14 | if os.path.exists(file): 15 | print(f"Removing the previous file: {file}") 16 | os.remove(file) 17 | # Create a list of the files that we want to combine 18 | all_filenames = [i for i in glob.glob('*.{}'.format(extension))] 19 | # combine all files in the list 20 | combined_csv = pd.concat([pd.read_csv(f) for f in all_filenames]) 21 | combined_csv.sort_values(by=["Service", "Policy Definition"], inplace=True) 22 | # export to csv 23 | combined_csv.drop_duplicates(subset="Policy Definition", keep=False, inplace=False) 24 | combined_csv.to_csv(file, index=False, encoding='utf-8-sig') 25 | 26 | 27 | if __name__ == '__main__': 28 | combine_csvs() 29 | -------------------------------------------------------------------------------- /utils/terraform-demo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | 4 | SUBSCRIPTION_NAME="${SUBSCRIPTION_NAME:=example}" 5 | FOLDER="${FOLDER:=examples}" 6 | 7 | # Validate that cloud-guardrails tool is installed 8 | if ! command -v cloud-guardrails &> /dev/null 9 | then 10 | echo "cloud-guardrails could not be found. Please download and install the tool from https://github.com/salesforce/cloud-guardrails/" 11 | exit 12 | fi 13 | 14 | # Validate that Terraform 0.12 is installed and in use 15 | is_tf_version_12=$(terraform version | grep -m1 "" | grep -m1 "0\.12\."); 16 | 17 | if [[ -z $is_tf_version_12 ]]; then 18 | echo "Terraform 0.12.x is NOT used. Let's try to install it with tfenv"; 19 | # If tfenv is not installed 20 | if ! command -v tfenv &> /dev/null 21 | then 22 | echo "tfenv could not be found. Please download and install the tool from https://github.com/tfutils/tfenv" 23 | exit 24 | fi 25 | # If tfenv exists, install Terraform 0.12.x 26 | tfenv install 0.12.28 27 | tfenv use 0.12.28 28 | else 29 | echo "Terraform 0.12.x is used. We can leverage that for running terraform validate"; 30 | fi 31 | 32 | if ! command -v cloud-guardrails &> /dev/null 33 | then 34 | echo "cloud-guardrails could not be found. Please download and install the tool from https://github.com/salesforce/cloud-guardrails/" 35 | exit 36 | fi 37 | 38 | # Create Folders 39 | export no_params_folder="${FOLDER}/terraform-demo-no-params" 40 | mkdir -p ${no_params_folder} 41 | export params_optional_folder="${FOLDER}/terraform-demo-params-optional" 42 | mkdir -p ${params_optional_folder} 43 | export params_required_folder="${FOLDER}/terraform-demo-params-required" 44 | mkdir -p ${params_required_folder} 45 | 46 | 47 | #### Generate the example Terraform files 48 | # No Parameters 49 | cloud-guardrails generate-terraform --no-params \ 50 | --service all \ 51 | --subscription ${SUBSCRIPTION_NAME} \ 52 | --output ${no_params_folder} 53 | 54 | # Optional Parameters 55 | cloud-guardrails generate-terraform --params-optional \ 56 | --service all \ 57 | --subscription ${SUBSCRIPTION_NAME} \ 58 | --output ${params_optional_folder} 59 | 60 | # Required Parameters 61 | cloud-guardrails generate-terraform --params-required \ 62 | --service all \ 63 | --subscription ${SUBSCRIPTION_NAME} \ 64 | --output ${params_required_folder} 65 | 66 | # Run Terraform validate inside there 67 | echo "Running Terraform validate" 68 | pwd 69 | declare -a dirs=( ${no_params_folder} ${params_optional_folder} ${params_required_folder} ) 70 | 71 | for dir in ${dirs[@]}; do 72 | cd ./$dir/ 73 | echo "Running terraform validate in $${dir}.."; 74 | terraform init -backend=false 75 | terraform validate 76 | echo $! 77 | cd ../../ 78 | done 79 | -------------------------------------------------------------------------------- /utils/update-policy-table.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | 4 | cloud-guardrails --help 5 | 6 | # Run generate-terraform for all 3 types 7 | cloud-guardrails generate-terraform --service all --subscription example --no-params 8 | cloud-guardrails generate-terraform --service all --subscription example --params-optional 9 | cloud-guardrails generate-terraform --service all --subscription example --params-required 10 | 11 | # Copy files to the docs directory 12 | mv NP-all-table.csv docs/summaries/no-params.csv 13 | mv NP-all-table.md docs/summaries/no-params.md 14 | mv PO-all-table.csv docs/summaries/params-optional.csv 15 | mv PO-all-table.md docs/summaries/params-optional.md 16 | mv PR-all-table.csv docs/summaries/params-required.csv 17 | mv PR-all-table.md docs/summaries/params-required.md 18 | 19 | rm no_params.tf 20 | rm params_optional.tf 21 | rm params_required.tf 22 | 23 | python3 ./utils/combine_csvs.py 24 | --------------------------------------------------------------------------------