├── .ansible-lint ├── .config ├── .gitleaks-report.json └── .secrets.baseline ├── .gitattributes ├── .github ├── .DS_Store └── workflows │ ├── devel_pipeline_validation.yml │ ├── main_pipeline_validation.yml │ └── update_galaxy.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .yamllint ├── CONTRIBUTING.rst ├── Changelog.md ├── LICENSE ├── README.md ├── collections └── requirements.yml ├── defaults └── main.yml ├── handlers └── main.yml ├── meta └── main.yml ├── site.yml ├── tasks ├── LE_audit_setup.yml ├── audit_only.yml ├── fetch_audit_output.yml ├── main.yml ├── parse_etc_password.yml ├── post.yml ├── post_remediation_audit.yml ├── pre_remediation_audit.yml ├── prelim.yml ├── section_1 │ ├── cis_1.1.1.x.yml │ ├── cis_1.1.10.yml │ ├── cis_1.1.2.x.yml │ ├── cis_1.1.3.x.yml │ ├── cis_1.1.4.x.yml │ ├── cis_1.1.5.x.yml │ ├── cis_1.1.6.x.yml │ ├── cis_1.1.7.x.yml │ ├── cis_1.1.8.x.yml │ ├── cis_1.1.9.yml │ ├── cis_1.2.x.yml │ ├── cis_1.3.x.yml │ ├── cis_1.4.x.yml │ ├── cis_1.5.x.yml │ ├── cis_1.6.x.yml │ ├── cis_1.7.x.yml │ ├── cis_1.8.x.yml │ └── main.yml ├── section_2 │ ├── cis_2.1.1.x.yml │ ├── cis_2.1.2.x.yml │ ├── cis_2.1.3.x.yml │ ├── cis_2.1.4.x.yml │ ├── cis_2.2.x.yml │ ├── cis_2.3.x.yml │ ├── cis_2.4.yml │ └── main.yml ├── section_3 │ ├── cis_3.1.x.yml │ ├── cis_3.2.x.yml │ ├── cis_3.3.x.yml │ ├── cis_3.4.1.x.yml │ ├── cis_3.4.2.x.yml │ ├── cis_3.4.3.x.yml │ └── main.yml ├── section_4 │ ├── cis_4.1.x.yml │ ├── cis_4.2.x.yml │ ├── cis_4.3.x.yml │ ├── cis_4.4.x.yml │ ├── cis_4.5.1.x.yml │ ├── cis_4.5.x.yml │ └── main.yml ├── section_5 │ ├── cis_5.1.1.1.x.yml │ ├── cis_5.1.1.x.yml │ ├── cis_5.1.2.x.yml │ ├── cis_5.1.3.yml │ ├── cis_5.2.1.x.yml │ ├── cis_5.2.2.x.yml │ ├── cis_5.2.3.x.yml │ ├── cis_5.2.4.x.yml │ └── main.yml ├── section_6 │ ├── cis_6.1.x.yml │ ├── cis_6.2.x.yml │ └── main.yml └── warning_facts.yml ├── templates ├── .DS_Store ├── ansible_vars_goss.yml.j2 ├── audit │ ├── ubtu20cis_5_2_3_10_mount.rules.j2 │ ├── ubtu20cis_5_2_3_11_session.rules.j2 │ ├── ubtu20cis_5_2_3_12_logins.rules.j2 │ ├── ubtu20cis_5_2_3_13_delete.rules.j2 │ ├── ubtu20cis_5_2_3_14_macpolicy.rules.j2 │ ├── ubtu20cis_5_2_3_19_modules.rules.j2 │ ├── ubtu20cis_5_2_3_1_scope.rules.j2 │ ├── ubtu20cis_5_2_3_20_finalize.rules.j2 │ ├── ubtu20cis_5_2_3_2_user_emulation.rules.j2 │ ├── ubtu20cis_5_2_3_3_sudo_log.rules.j2 │ ├── ubtu20cis_5_2_3_4_timechange.rules.j2 │ ├── ubtu20cis_5_2_3_5_systemlocale.rules.j2 │ ├── ubtu20cis_5_2_3_6_privileged.rules.j2 │ ├── ubtu20cis_5_2_3_7_access.rules.j2 │ ├── ubtu20cis_5_2_3_8_identity.rules.j2 │ ├── ubtu20cis_5_2_3_9_permmod.rules.j2 │ └── ubtu20cis_5_2_3_x_perm_chng.rules.j2 ├── chrony.conf.j2 ├── etc │ ├── ansible │ │ └── compliance_facts.j2 │ ├── chrony │ │ └── sources.d │ │ │ └── time.sources.j2 │ ├── issue.j2 │ ├── issue.net.j2 │ ├── motd.j2 │ └── systemd │ │ ├── system │ │ └── tmp.mount.j2 │ │ └── timesyncd.conf.d │ │ └── 50-timesyncd.conf.j2 └── ntp.conf.j2 └── vars ├── audit.yml └── main.yml /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | parseable: true 4 | quiet: true 5 | skip_list: 6 | - 'schema' 7 | - 'no-changed-when' 8 | - 'var-spacing' 9 | - 'fqcn-builtins' 10 | - 'experimental' 11 | - 'name[play]' 12 | - 'name[casing]' 13 | - 'name[template]' 14 | - 'fqcn[action]' 15 | - 'key-order[task]' 16 | - '204' 17 | - '305' 18 | - '303' 19 | - '403' 20 | - '306' 21 | - '602' 22 | - '208' 23 | use_default_rules: true 24 | verbosity: 0 25 | -------------------------------------------------------------------------------- /.config/.gitleaks-report.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /.config/.secrets.baseline: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.4.0", 3 | "plugins_used": [ 4 | { 5 | "name": "ArtifactoryDetector" 6 | }, 7 | { 8 | "name": "AWSKeyDetector" 9 | }, 10 | { 11 | "name": "AzureStorageKeyDetector" 12 | }, 13 | { 14 | "name": "Base64HighEntropyString", 15 | "limit": 4.5 16 | }, 17 | { 18 | "name": "BasicAuthDetector" 19 | }, 20 | { 21 | "name": "CloudantDetector" 22 | }, 23 | { 24 | "name": "DiscordBotTokenDetector" 25 | }, 26 | { 27 | "name": "GitHubTokenDetector" 28 | }, 29 | { 30 | "name": "HexHighEntropyString", 31 | "limit": 3.0 32 | }, 33 | { 34 | "name": "IbmCloudIamDetector" 35 | }, 36 | { 37 | "name": "IbmCosHmacDetector" 38 | }, 39 | { 40 | "name": "JwtTokenDetector" 41 | }, 42 | { 43 | "name": "KeywordDetector", 44 | "keyword_exclude": "" 45 | }, 46 | { 47 | "name": "MailchimpDetector" 48 | }, 49 | { 50 | "name": "NpmDetector" 51 | }, 52 | { 53 | "name": "PrivateKeyDetector" 54 | }, 55 | { 56 | "name": "SendGridDetector" 57 | }, 58 | { 59 | "name": "SlackDetector" 60 | }, 61 | { 62 | "name": "SoftlayerDetector" 63 | }, 64 | { 65 | "name": "SquareOAuthDetector" 66 | }, 67 | { 68 | "name": "StripeDetector" 69 | }, 70 | { 71 | "name": "TwilioKeyDetector" 72 | } 73 | ], 74 | "filters_used": [ 75 | { 76 | "path": "detect_secrets.filters.allowlist.is_line_allowlisted" 77 | }, 78 | { 79 | "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", 80 | "min_level": 2 81 | }, 82 | { 83 | "path": "detect_secrets.filters.heuristic.is_indirect_reference" 84 | }, 85 | { 86 | "path": "detect_secrets.filters.heuristic.is_likely_id_string" 87 | }, 88 | { 89 | "path": "detect_secrets.filters.heuristic.is_lock_file" 90 | }, 91 | { 92 | "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" 93 | }, 94 | { 95 | "path": "detect_secrets.filters.heuristic.is_potential_uuid" 96 | }, 97 | { 98 | "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" 99 | }, 100 | { 101 | "path": "detect_secrets.filters.heuristic.is_sequential_string" 102 | }, 103 | { 104 | "path": "detect_secrets.filters.heuristic.is_swagger_file" 105 | }, 106 | { 107 | "path": "detect_secrets.filters.heuristic.is_templated_secret" 108 | }, 109 | { 110 | "path": "detect_secrets.filters.regex.should_exclude_file", 111 | "pattern": [ 112 | ".config/.gitleaks-report.json", 113 | "tasks/parse_etc_password.yml" 114 | ] 115 | } 116 | ], 117 | "results": {}, 118 | "generated_at": "2023-09-19T12:32:59Z" 119 | } 120 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github 2 | # Default behaviour 3 | * text=auto 4 | 5 | # https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings 6 | # Ensure to read artcile prior to adding 7 | # Scripts should have Unix endings 8 | *.py text eol=lf 9 | *.sh text eol=lf 10 | 11 | # Windows Batch or PowerShell scripts should have CRLF endings 12 | *.bat text eol=crlf 13 | *.ps1 text eol=crlf 14 | 15 | # adding github settings to show correct language 16 | *.sh linguist-detectable=true 17 | *.yml linguist-detectable=true 18 | *.ps1 linguist-detectable=true 19 | *.j2 linguist-detectable=true 20 | *.md linguist-documentation 21 | -------------------------------------------------------------------------------- /.github/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-lockdown/UBUNTU20-CIS/4d88c984b88eb1abbbc7a54cb09f0085f853bbe1/.github/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/devel_pipeline_validation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Devel pipeline 4 | 5 | on: # yamllint disable-line rule:truthy 6 | pull_request_target: 7 | types: [opened, reopened, synchronize] 8 | branches: 9 | - devel 10 | - benchmark* 11 | paths: 12 | - '**.yml' 13 | - '**.sh' 14 | - '**.j2' 15 | - '**.ps1' 16 | - '**.cfg' 17 | # Allow manual running of workflow 18 | workflow_dispatch: 19 | 20 | # Allow permissions for AWS auth 21 | permissions: 22 | id-token: write 23 | contents: read 24 | pull-requests: read 25 | 26 | # A workflow run is made up of one or more jobs 27 | # that can run sequentially or in parallel 28 | jobs: 29 | # This will create messages for first time contributers and direct them to the Discord server 30 | welcome: 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - uses: actions/first-interaction@main 35 | with: 36 | repo-token: ${{ secrets.GITHUB_TOKEN }} 37 | pr-message: |- 38 | Congrats on opening your first pull request and thank you for taking the time to help improve Ansible-Lockdown! 39 | Please join in the conversation happening on the [Discord Server](https://www.lockdownenterprise.com/discord) as well. 40 | 41 | # This workflow contains a single job that tests the playbook 42 | playbook-test: 43 | # The type of runner that the job will run on 44 | runs-on: self-hosted 45 | env: 46 | ENABLE_DEBUG: ${{ vars.ENABLE_DEBUG }} 47 | # Imported as a variable by terraform 48 | TF_VAR_repository: ${{ github.event.repository.name }} 49 | AWS_REGION: "us-east-1" 50 | ANSIBLE_VERSION: ${{ vars.ANSIBLE_RUNNER_VERSION }} 51 | defaults: 52 | run: 53 | shell: bash 54 | working-directory: .github/workflows/github_linux_IaC 55 | # working-directory: .github/workflows 56 | 57 | steps: 58 | 59 | - name: Git clone the lockdown repository to test 60 | uses: actions/checkout@v4 61 | with: 62 | ref: ${{ github.event.pull_request.head.sha }} 63 | 64 | - name: If a variable for IAC_BRANCH is set use that branch 65 | working-directory: .github/workflows 66 | run: | 67 | if [ ${{ vars.IAC_BRANCH }} != '' ]; then 68 | echo "IAC_BRANCH=${{ vars.IAC_BRANCH }}" >> $GITHUB_ENV 69 | echo "Pipeline using the following IAC branch ${{ vars.IAC_BRANCH }}" 70 | else 71 | echo IAC_BRANCH=main >> $GITHUB_ENV 72 | fi 73 | 74 | # Pull in terraform code for linux servers 75 | - name: Clone GitHub IaC plan 76 | uses: actions/checkout@v4 77 | with: 78 | repository: ansible-lockdown/github_linux_IaC 79 | path: .github/workflows/github_linux_IaC 80 | ref: ${{ env.IAC_BRANCH }} 81 | 82 | # Uses dedicated restricted role and policy to enable this only for this task 83 | # No credentials are part of github for AWS auth 84 | - name: configure aws credentials 85 | uses: aws-actions/configure-aws-credentials@main 86 | with: 87 | role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} 88 | role-session-name: ${{ secrets.AWS_ROLE_SESSION }} 89 | aws-region: ${{ env.AWS_REGION }} 90 | 91 | - name: DEBUG - Show IaC files 92 | if: env.ENABLE_DEBUG == 'true' 93 | run: | 94 | echo "OSVAR = $OSVAR" 95 | echo "benchmark_type = $benchmark_type" 96 | echo "PRIVSUBNET_ID = $AWS_PRIVSUBNET_ID" 97 | echo "VPC_ID" = $AWS_VPC_SECGRP_ID" 98 | pwd 99 | ls 100 | env: 101 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 102 | OSVAR: ${{ vars.OSVAR }} 103 | benchmark_type: ${{ vars.BENCHMARK_TYPE }} 104 | PRIVSUBNET_ID: ${{ secrets.AWS_PRIVSUBNET_ID }} 105 | VPC_ID: ${{ secrets.AWS_VPC_SECGRP_ID }} 106 | 107 | - name: Tofu init 108 | id: init 109 | run: tofu init 110 | env: 111 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 112 | OSVAR: ${{ vars.OSVAR }} 113 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 114 | 115 | - name: Tofu validate 116 | id: validate 117 | run: tofu validate 118 | env: 119 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 120 | OSVAR: ${{ vars.OSVAR }} 121 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 122 | 123 | - name: Tofu apply 124 | id: apply 125 | env: 126 | OSVAR: ${{ vars.OSVAR }} 127 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 128 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 129 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 130 | run: tofu apply -var-file "${OSVAR}.tfvars" --auto-approve -input=false 131 | 132 | ## Debug Section 133 | - name: DEBUG - Show Ansible hostfile 134 | if: env.ENABLE_DEBUG == 'true' 135 | run: cat hosts.yml 136 | 137 | # Aws deployments taking a while to come up insert sleep or playbook fails 138 | 139 | - name: Sleep to allow system to come up 140 | run: sleep ${{ vars.BUILD_SLEEPTIME }} 141 | 142 | # Run the Ansible playbook 143 | - name: Run_Ansible_Playbook 144 | env: 145 | ANSIBLE_HOST_KEY_CHECKING: "false" 146 | ANSIBLE_DEPRECATION_WARNINGS: "false" 147 | run: | 148 | /opt/ansible_${{ env.ANSIBLE_VERSION }}_venv/bin/ansible-playbook -i hosts.yml --private-key ~/.ssh/le_runner ../../../site.yml 149 | 150 | # Remove test system - User secrets to keep if necessary 151 | 152 | - name: Tofu Destroy 153 | if: always() && env.ENABLE_DEBUG == 'false' 154 | env: 155 | OSVAR: ${{ vars.OSVAR }} 156 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 157 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 158 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 159 | run: tofu destroy -var-file "${OSVAR}.tfvars" --auto-approve -input=false 160 | -------------------------------------------------------------------------------- /.github/workflows/main_pipeline_validation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Main pipeline 4 | 5 | on: # yamllint disable-line rule:truthy 6 | pull_request_target: 7 | types: [opened, reopened, synchronize] 8 | branches: 9 | - main 10 | - latest 11 | paths: 12 | - '**.yml' 13 | - '**.sh' 14 | - '**.j2' 15 | - '**.ps1' 16 | - '**.cfg' 17 | 18 | # Allow permissions for AWS auth 19 | permissions: 20 | id-token: write 21 | contents: read 22 | pull-requests: read 23 | 24 | # A workflow run is made up of one or more jobs 25 | # that can run sequentially or in parallel 26 | jobs: 27 | 28 | # This workflow contains a single job that tests the playbook 29 | playbook-test: 30 | # The type of runner that the job will run on 31 | runs-on: self-hosted 32 | env: 33 | ENABLE_DEBUG: ${{ vars.ENABLE_DEBUG }} 34 | # Imported as a variable by terraform 35 | TF_VAR_repository: ${{ github.event.repository.name }} 36 | AWS_REGION : "us-east-1" 37 | ANSIBLE_VERSION: ${{ vars.ANSIBLE_RUNNER_VERSION }} 38 | defaults: 39 | run: 40 | shell: bash 41 | working-directory: .github/workflows/github_linux_IaC 42 | # working-directory: .github/workflows 43 | 44 | steps: 45 | 46 | - name: Git clone the lockdown repository to test 47 | uses: actions/checkout@v4 48 | with: 49 | ref: ${{ github.event.pull_request.head.sha }} 50 | 51 | - name: If a variable for IAC_BRANCH is set use that branch 52 | working-directory: .github/workflows 53 | run: | 54 | if [ ${{ vars.IAC_BRANCH }} != '' ]; then 55 | echo "IAC_BRANCH=${{ vars.IAC_BRANCH }}" >> $GITHUB_ENV 56 | echo "Pipeline using the following IAC branch ${{ vars.IAC_BRANCH }}" 57 | else 58 | echo IAC_BRANCH=main >> $GITHUB_ENV 59 | fi 60 | 61 | # Pull in terraform code for linux servers 62 | - name: Clone GitHub IaC plan 63 | uses: actions/checkout@v4 64 | with: 65 | repository: ansible-lockdown/github_linux_IaC 66 | path: .github/workflows/github_linux_IaC 67 | ref: ${{ env.IAC_BRANCH }} 68 | 69 | # Uses dedicated restricted role and policy to enable this only for this task 70 | # No credentials are part of github for AWS auth 71 | - name: configure aws credentials 72 | uses: aws-actions/configure-aws-credentials@main 73 | with: 74 | role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} 75 | role-session-name: ${{ secrets.AWS_ROLE_SESSION }} 76 | aws-region: ${{ env.AWS_REGION }} 77 | 78 | - name: DEBUG - Show IaC files 79 | if: env.ENABLE_DEBUG == 'true' 80 | run: | 81 | echo "OSVAR = $OSVAR" 82 | echo "benchmark_type = $benchmark_type" 83 | echo "PRIVSUBNET_ID = $AWS_PRIVSUBNET_ID" 84 | echo "VPC_ID" = $AWS_VPC_SECGRP_ID" 85 | pwd 86 | ls 87 | env: 88 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 89 | OSVAR: ${{ vars.OSVAR }} 90 | benchmark_type: ${{ vars.BENCHMARK_TYPE }} 91 | PRIVSUBNET_ID: ${{ secrets.AWS_PRIVSUBNET_ID }} 92 | VPC_ID: ${{ secrets.AWS_VPC_SECGRP_ID }} 93 | 94 | - name: Tofu init 95 | id: init 96 | run: tofu init 97 | env: 98 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 99 | OSVAR: ${{ vars.OSVAR }} 100 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 101 | 102 | - name: Tofu validate 103 | id: validate 104 | run: tofu validate 105 | env: 106 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 107 | OSVAR: ${{ vars.OSVAR }} 108 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 109 | 110 | - name: Tofu apply 111 | id: apply 112 | env: 113 | OSVAR: ${{ vars.OSVAR }} 114 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 115 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 116 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 117 | run: tofu apply -var-file "${OSVAR}.tfvars" --auto-approve -input=false 118 | 119 | ## Debug Section 120 | - name: DEBUG - Show Ansible hostfile 121 | if: env.ENABLE_DEBUG == 'true' 122 | run: cat hosts.yml 123 | 124 | # Aws deployments taking a while to come up insert sleep or playbook fails 125 | 126 | - name: Sleep to allow system to come up 127 | run: sleep ${{ vars.BUILD_SLEEPTIME }} 128 | 129 | # Run the Ansible playbook 130 | - name: Run_Ansible_Playbook 131 | env: 132 | ANSIBLE_HOST_KEY_CHECKING: "false" 133 | ANSIBLE_DEPRECATION_WARNINGS: "false" 134 | run: | 135 | /opt/ansible_${{ env.ANSIBLE_VERSION }}_venv/bin/ansible-playbook -i hosts.yml --private-key ~/.ssh/le_runner ../../../site.yml 136 | 137 | # Remove test system - User secrets to keep if necessary 138 | 139 | - name: Tofu Destroy 140 | if: always() && env.ENABLE_DEBUG == 'false' 141 | env: 142 | OSVAR: ${{ vars.OSVAR }} 143 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 144 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 145 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 146 | run: tofu destroy -var-file "${OSVAR}.tfvars" --auto-approve -input=false 147 | -------------------------------------------------------------------------------- /.github/workflows/update_galaxy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: update galaxy 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | jobs: 10 | update_role: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v4 15 | 16 | - name: Action Ansible Galaxy Release ${{ github.ref_name }} 17 | uses: ansible-actions/ansible-galaxy-action@main 18 | with: 19 | galaxy_api_key: ${{ secrets.GALAXY_API_KEY }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.log 3 | *.retry 4 | .vagrant 5 | tests/*redhat-subscription 6 | tests/Dockerfile 7 | *.iso 8 | *.box 9 | packer_cache 10 | delete* 11 | ignore* 12 | # VSCode 13 | .vscode 14 | vagrant 15 | 16 | # Byte-compiled / optimized / DLL files 17 | __pycache__/ 18 | *.py[cod] 19 | *$py.class 20 | 21 | # DS_Store 22 | .DS_Store 23 | ._* 24 | 25 | # Linux Editors 26 | *~ 27 | \#*\# 28 | /.emacs.desktop 29 | /.emacs.desktop.lock 30 | .elc 31 | auto-save-list 32 | tramp 33 | .\#* 34 | *.swp 35 | *.swo 36 | rh-creds.env 37 | travis.env 38 | 39 | # Lockdown-specific 40 | benchparse/ 41 | *xccdf.xml 42 | *.retry 43 | 44 | # GitHub Action/Workflow files 45 | .github/ 46 | 47 | # ansible-lint cache 48 | .ansible/ 49 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ##### CI for use by github no need for action to be added 3 | ##### Inherited 4 | ci: 5 | autofix_prs: false 6 | skip: [detect-aws-credentials, ansible-lint ] 7 | 8 | repos: 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v5.0.0 11 | hooks: 12 | # Safety 13 | - id: detect-aws-credentials 14 | - id: detect-private-key 15 | 16 | # git checks 17 | - id: check-merge-conflict 18 | - id: check-added-large-files 19 | - id: check-case-conflict 20 | 21 | # General checks 22 | - id: trailing-whitespace 23 | name: Trim Trailing Whitespace 24 | description: This hook trims trailing whitespace. 25 | entry: trailing-whitespace-fixer 26 | language: python 27 | types: [text] 28 | args: [--markdown-linebreak-ext=md] 29 | - id: end-of-file-fixer 30 | 31 | # Scan for passwords 32 | - repo: https://github.com/Yelp/detect-secrets 33 | rev: v1.5.0 34 | hooks: 35 | - id: detect-secrets 36 | args: [ '--baseline', '.config/.secrets.baseline' ] 37 | exclude: .config/.gitleaks-report.json tasks/parse_etc_password 38 | 39 | - repo: https://github.com/gitleaks/gitleaks 40 | rev: v8.26.0 41 | hooks: 42 | - id: gitleaks 43 | args: ['--baseline-path', '.config/.gitleaks-report.json'] 44 | exclude: .config/.secrets.baseline 45 | 46 | - repo: https://github.com/ansible-community/ansible-lint 47 | rev: v25.5.0 48 | hooks: 49 | - id: ansible-lint 50 | name: Ansible-lint 51 | description: This hook runs ansible-lint. 52 | entry: python3 -m ansiblelint --force-color site.yml -c .ansible-lint 53 | language: python 54 | # do not pass files to ansible-lint, see: 55 | # https://github.com/ansible/ansible-lint/issues/611 56 | pass_filenames: false 57 | always_run: true 58 | additional_dependencies: 59 | # https://github.com/pre-commit/pre-commit/issues/1526 60 | # If you want to use specific version of ansible-core or ansible, feel 61 | # free to override `additional_dependencies` in your own hook config 62 | # file. 63 | - ansible-core>=2.10.1 64 | 65 | - repo: https://github.com/adrienverge/yamllint.git 66 | rev: v1.37.1 # or higher tag 67 | hooks: 68 | - id: yamllint 69 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | ignore: | 5 | tests/ 6 | molecule/ 7 | .github/ 8 | .gitlab-ci.yml 9 | *molecule.yml 10 | 11 | rules: 12 | indentation: 13 | # Requiring 4 space indentation 14 | spaces: 4 15 | # Requiring consistent indentation within a file, either indented or not 16 | indent-sequences: consistent 17 | braces: 18 | max-spaces-inside: 1 19 | level: error 20 | brackets: 21 | max-spaces-inside: 1 22 | level: error 23 | empty-lines: 24 | max: 1 25 | line-length: disable 26 | key-duplicates: enable 27 | new-line-at-end-of-file: enable 28 | new-lines: 29 | type: unix 30 | trailing-spaces: enable 31 | truthy: 32 | allowed-values: ['true', 'false'] 33 | check-keys: true 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing to MindPoint Group Projects 2 | ======================================== 3 | 4 | Rules 5 | ----- 6 | 1) All commits must be GPG signed (details in Signing section) 7 | 2) All commits must have Signed-off-by (Signed-off-by: Joan Doe ) in the commit message (details in Signing section) 8 | 3) All work is done in your own branch or own fork 9 | 4) Pull requests 10 | a) From within the repo: All pull requests go into the devel branch. There are automated checks for signed commits, signoff in commit message, and functional testing 11 | b) From a forked repo: All pull requests will go into a staging branch within the repo. There are automated checks for signed commits, signoff in commit message, and functional testing when going from staging to devel 12 | 4) All pull requests go into the devel branch. There are automated checks for signed commits, signoff in commit message, and functional testing) 13 | 5) Be open and nice to each other 14 | 15 | Workflow 16 | -------- 17 | - Your work is done in your own individual branch. Make sure to to Signed-off and GPG sign all commits you intend to merge 18 | - All community Pull Requests are into the devel branch. There are automated checks for GPG signed, Signed-off in commits, and functional tests before being approved. If your pull request comes in from outside of our repo, the pull request will go into a staging branch. There is info needed from our repo for our CI/CD testing. 19 | - Once your changes are merged and a more detailed review is complete, an authorized member will merge your changes into the main branch for a new release 20 | Signing your contribution 21 | ------------------------- 22 | 23 | We've chosen to use the Developer's Certificate of Origin (DCO) method 24 | that is employed by the Linux Kernel Project, which provides a simple 25 | way to contribute to MindPoint Group projects. 26 | 27 | The process is to certify the below DCO 1.1 text 28 | :: 29 | 30 | Developer's Certificate of Origin 1.1 31 | 32 | By making a contribution to this project, I certify that: 33 | 34 | (a) The contribution was created in whole or in part by me and I 35 | have the right to submit it under the open source license 36 | indicated in the file; or 37 | 38 | (b) The contribution is based upon previous work that, to the best 39 | of my knowledge, is covered under an appropriate open source 40 | license and I have the right under that license to submit that 41 | work with modifications, whether created in whole or in part 42 | by me, under the same open source license (unless I am 43 | permitted to submit under a different license), as indicated 44 | in the file; or 45 | 46 | (c) The contribution was provided directly to me by some other 47 | person who certified (a), (b) or (c) and I have not modified 48 | it. 49 | 50 | (d) I understand and agree that this project and the contribution 51 | are public and that a record of the contribution (including all 52 | personal information I submit with it, including my sign-off) is 53 | maintained indefinitely and may be redistributed consistent with 54 | this project or the open source license(s) involved. 55 | :: 56 | 57 | Then, when it comes time to submit a contribution, include the 58 | following text in your contribution commit message: 59 | 60 | :: 61 | 62 | Signed-off-by: Joan Doe 63 | 64 | :: 65 | 66 | 67 | This message can be entered manually, or if you have configured git 68 | with the correct `user.name` and `user.email`, you can use the `-s` 69 | option to `git commit` to automatically include the signoff message. 70 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Change log for Ubuntu 2004 2 | 3 | ## v2.0.1 based on CIS v2.0.1 4 | 5 | - issue 148 thanks to @karlg100 6 | - workflow updates for new pipeline 7 | - audit 8 | - updated files and variables 9 | - updated vars/audit.yml 10 | - improved when using local copies or archived 11 | 12 | ## v2.0.1 based upon CIS 2.0.1 13 | 14 | - ability to run goss audit only audit_only variable 15 | - audit vars mainly moved to var/audit.yml 16 | - several control updates 17 | - goss version update to 0.4.4 18 | 19 | ## V2.0 based upon CIS 2.0.1 20 | 21 | - v2.0.1 - refer to change history from official CIS pdf. 22 | - ReWrite of many rules 23 | - Ordering and numbering of rules 24 | - many title updates 25 | - timesync options increased 26 | - default systemd-timesyncd 27 | - chrony options updated 28 | - idempotency improvements 29 | - new discoveries 30 | - interactive users 31 | - uid min value 32 | - is_container discovery and default var 33 | - pre-commit added to setup 34 | - README new layout 35 | 36 | - Added test for rule 4.3.4 check user is using sudo has password set before NOPASSWD removed from sudoers 37 | - grub password check update thanks to @Acenl12 on discord 38 | 39 | ## V1.0.1 - based upon CIS 1.1.0 40 | 41 | thanks to ikthomas 42 | [#84](https://github.com/ansible-lockdown/UBUNTU20-CIS/issues/84) 43 | 44 | ## v1.0.0 45 | 46 | - update galaxy lint requirements 47 | - license file 48 | - ansible version 49 | 50 | ## April 2023 Updates 51 | - Addressed Bugs 52 | - [#73](https://github.com/ansible-lockdown/UBUNTU20-CIS/issues/73) - Thanks @fnschroeder (Fix Taken From @uk-bolly issue_73 branch) 53 | - [#80](https://github.com/ansible-lockdown/UBUNTU20-CIS/issues/80) - Thanks @kdebisschop 54 | - Added Fixes For Outstanding PR's 55 | - [#81](https://github.com/ansible-lockdown/UBUNTU20-CIS/pull/81) - Thanks @kdebisschop 56 | - Fixed Linting Errors For Yamllint & Ansbile-Lint 57 | - Adjusted Builtin to Posix For sysctl module. 58 | 59 | ## Feb 23 updates - Initial 60 | 61 | - lint files updated 62 | - ansible version updated 63 | - Lots of lint and standardisation changes 64 | - fqcn 65 | - Assertions for root and grub passwords 66 | - Import tasks to allow tags to be used 67 | - Warnings made standard 68 | - warn count feature added 69 | - workflow updates 70 | - wireless interface discovery 71 | - idempotency checks and updates 72 | 73 | reboot variable changed from ubtu20_skip_reboot to skip_reboot (still default true) 74 | 75 | ### Remediate portion 76 | 77 | ### Issues and PRs address 78 | 79 | - #1 set bootloader pwd - Allowed unrestricted by default but set new variables 80 | - Added extra variable options ubtu20cis_set_grub_password and ubtu20cis_set_root_password (defaults true) 81 | 82 | - #2 Ensure locks for failed attempts 83 | - #3 root path integrity 84 | - thanks to @vbotka 85 | - #63 parse_etc_password 86 | - thanks to @makefu 87 | - #67 UFW incoming firewall ports (optional) 88 | - thanks to @CFoltin 89 | - #68 logrotate alignment 90 | - #69 stop rule overwrite UFW 91 | - thanks to @hackery 92 | - #70 TMOUT stops being repeated 93 | 94 | Many improvements on multiple controls 95 | Remediate and audit version now match. When using remediate will pull in latest version of audit for that release. 96 | 97 | ### Audit 98 | 99 | - updated goss version used 100 | - aligned new variables with audit 101 | - audit path used now default to /opt from /var/tmp 102 | 103 | ## Started at devel version 1.1.0 Feb_23 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mindpoint Group - A Tyto Athene Company / Ansible Lockdown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ubuntu 20 CIS 2 | 3 | ## Configure a Ubuntu 20 machine to be [CIS](https://www.cisecurity.org/cis-benchmarks/) compliant 4 | 5 | ### Based on CIS Ubuntu Linux 20.04 LTS Benchmark v2.0.1 [Release](https://learn.cisecurity.org/l/799323/2023-06-27/4t1gmr) 6 | 7 | ![Org Stars](https://img.shields.io/github/stars/ansible-lockdown?label=Org%20Stars&style=social) 8 | ![Stars](https://img.shields.io/github/stars/ansible-lockdown/ubuntu20-cis?label=Repo%20Stars&style=social) 9 | ![Forks](https://img.shields.io/github/forks/ansible-lockdown/ubuntu20-cis?style=social) 10 | ![followers](https://img.shields.io/github/followers/ansible-lockdown?style=social) 11 | [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/AnsibleLockdown.svg?style=social&label=Follow%20%40AnsibleLockdown)](https://twitter.com/AnsibleLockdown) 12 | 13 | ![Discord Badge](https://img.shields.io/discord/925818806838919229?logo=discord) 14 | 15 | ![Release Branch](https://img.shields.io/badge/Release%20Branch-Main-brightgreen) 16 | ![Release Tag](https://img.shields.io/github/v/release/ansible-lockdown/UBUNTU20-CIS) 17 | ![Release Date](https://img.shields.io/github/release-date/ansible-lockdown/UBUNTU20-CIS) 18 | 19 | [![Main Pipeline Status](https://github.com/ansible-lockdown/UBUNTU20-CIS/actions/workflows/main_pipeline_validation.yml/badge.svg?)](https://github.com/ansible-lockdown/UBUNTU20-CIS/actions/workflows/main_pipeline_validation.yml) 20 | 21 | [![Devel Pipeline Status](https://github.com/ansible-lockdown/UBUNTU20-CIS/actions/workflows/devel_pipeline_validation.yml/badge.svg?)](https://github.com/ansible-lockdown/UBUNTU20-CIS/actions/workflows/devel_pipeline_validation.yml) 22 | ![Devel Commits](https://img.shields.io/github/commit-activity/m/ansible-lockdown/UBUNTU20-CIS/devel?color=dark%20green&label=Devel%20Branch%20Commits) 23 | 24 | ![Issues Open](https://img.shields.io/github/issues-raw/ansible-lockdown/UBUNTU20-CIS?label=Open%20Issues) 25 | ![Issues Closed](https://img.shields.io/github/issues-closed-raw/ansible-lockdown/UBUNTU20-CIS?label=Closed%20Issues&&color=success) 26 | ![Pull Requests](https://img.shields.io/github/issues-pr/ansible-lockdown/UBUNTU20-CIS?label=Pull%20Requests) 27 | 28 | ![License](https://img.shields.io/github/license/ansible-lockdown/UBUNTU20-CIS?label=License) 29 | 30 | --- 31 | 32 | ## Looking for support? 33 | 34 | [Lockdown Enterprise](https://www.lockdownenterprise.com#GH_AL_UB20_cis) 35 | 36 | [Ansible support](https://www.mindpointgroup.com/cybersecurity-products/ansible-counselor#GH_AL_UB20_cis) 37 | 38 | ### Community 39 | 40 | On our [Discord Server](https://www.lockdownenterprise.com/discord) to ask questions, discuss features, or just chat with other Ansible-Lockdown users 41 | 42 | ## Caution(s) 43 | 44 | This role **will make changes to the system** that could break things. This is not an auditing tool but rather a remediation tool to be used after an audit has been conducted. 45 | 46 | This role was developed against a clean install of the Operating System. If you are implimenting to an existing system please review this role for any site specific changes that are needed. 47 | 48 | ## Documentation 49 | 50 | - [Read The Docs](https://ansible-lockdown.readthedocs.io/en/latest/) 51 | - [Getting Started](https://www.lockdownenterprise.com/docs/getting-started-with-lockdown#GH_AL_UB20_cis) 52 | - [Customizing Roles](https://www.lockdownenterprise.com/docs/customizing-lockdown-enterprise#GH_AL_UB20_cis) 53 | - [Per-Host Configuration](https://www.lockdownenterprise.com/docs/per-host-lockdown-enterprise-configuration#GH_AL_UB20_cis) 54 | - [Getting the Most Out of the Role](https://www.lockdownenterprise.com/docs/get-the-most-out-of-lockdown-enterprise#GH_AL_UB20_cis) 55 | 56 | ## Requirements 57 | 58 | **General:** 59 | 60 | - Basic knowledge of Ansible, below are some links to the Ansible documentation to help get started if you are unfamiliar with Ansible 61 | - [Main Ansible documentation page](https://docs.ansible.com) 62 | - [Ansible Getting Started](https://docs.ansible.com/ansible/latest/user_guide/intro_getting_started.html) 63 | - [Tower User Guide](https://docs.ansible.com/ansible-tower/latest/html/userguide/index.html) 64 | - [Ansible Community Info](https://docs.ansible.com/ansible/latest/community/index.html) 65 | - Functioning Ansible and/or Tower Installed, configured, and running. This includes all of the base Ansible/Tower configurations, needed packages installed, and infrastructure setup. 66 | - Please read through the tasks in this role to gain an understanding of what each control is doing. Some of the tasks are disruptive and can have unintended consiquences in a live production system. Also familiarize yourself with the variables in the defaults/main.yml file. 67 | **Technical Dependencies:** 68 | 69 | - Running Ansible/Tower setup (this role is tested against Ansible version 2.9.1 and newer) 70 | - Python3 Ansible run environment 71 | 72 | ## Auditing (new) 73 | 74 | This can be turned on or off within the defaults/main.yml file with the variable run_audit. The value is false by default, please refer to the wiki for more details. 75 | 76 | This is a much quicker, very lightweight, checking (where possible) config compliance and live/running settings. 77 | 78 | A new form of auditing has been developed, by using a small (12MB) go binary called [goss](https://github.com/goss-org/goss) along with the relevant configurations to check. Without the need for infrastructure or other tooling. 79 | This audit will not only check the config has the correct setting but aims to capture if it is running with that configuration also trying to remove [false positives](https://www.mindpointgroup.com/blog/is-compliance-scanning-still-relevant/) in the process. 80 | 81 | Refer to [UBUNTU20-CIS-Audit](https://github.com/ansible-lockdown/UBUNTU20-CIS-Audit). 82 | 83 | Further audit documentation can be found at [Read The Docs](https://ansible-lockdown.readthedocs.io/en/latest/) 84 | 85 | ## Role Variables 86 | 87 | This role is designed that the end user should not have to edit the tasks themselves. All customizing should be done via the defaults/main.yml file or with extra vars within the project, job, workflow, etc. 88 | 89 | ## Branches 90 | 91 | - **devel** - This is the default branch and the working development branch. Community pull requests will pull into this branch 92 | - **main** - This is the release branch 93 | - **reports** - This is a protected branch for our scoring reports, no code should ever go here 94 | - **gh-pages** - This is the github pages branch 95 | - **all other branches** - Individual community member branches 96 | 97 | ## Community Contribution 98 | 99 | We encourage you (the community) to contribute to this role. Please read the rules below. 100 | 101 | - Your work is done in your own individual branch. Make sure to Signed-off and GPG sign all commits you intend to merge. 102 | - All community Pull Requests are pulled into the devel branch 103 | - Pull Requests into devel will confirm your commits have a GPG signature, Signed-off, and a functional test before being approved 104 | - Once your changes are merged and a more detailed review is complete, an authorized member will merge your changes into the main branch for a new release 105 | 106 | ## Pipeline Testing 107 | 108 | uses: 109 | 110 | - ansible-core 2.12 111 | - ansible collections - pulls in the latest version based on requirements file 112 | - runs the audit using the devel branch 113 | - This is an automated test that occurs on pull requests into devel 114 | 115 | ## Added Extras 116 | 117 | - [pre-commit](https://pre-commit.com) can be tested and can be run from within the directory 118 | 119 | ```sh 120 | pre-commit run 121 | ``` 122 | -------------------------------------------------------------------------------- /collections/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | collections: 4 | - name: community.general 5 | source: https://github.com/ansible-collections/community.general 6 | type: git 7 | 8 | - name: community.crypto 9 | source: https://github.com/ansible-collections/community.crypto 10 | type: git 11 | 12 | - name: ansible.posix 13 | source: https://github.com/ansible-collections/ansible.posix 14 | type: git 15 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Remount tmp 4 | ansible.posix.mount: 5 | name: /tmp 6 | state: remounted 7 | 8 | - name: remount dev_shm 9 | ansible.posix.mount: 10 | name: /dev/shm 11 | state: remounted 12 | 13 | - name: Remount var 14 | ansible.posix.mount: 15 | name: /var 16 | state: remounted 17 | 18 | - name: Remount var_tmp 19 | ansible.posix.mount: 20 | name: /var/tmp 21 | state: remounted 22 | 23 | - name: Remount var_log 24 | ansible.posix.mount: 25 | name: /var/log 26 | state: remounted 27 | 28 | - name: Remount var_log_audit 29 | ansible.posix.mount: 30 | name: /var/log/audit 31 | state: remounted 32 | 33 | - name: Remount home 34 | ansible.posix.mount: 35 | name: /home 36 | state: remounted 37 | 38 | - name: Grub update 39 | ansible.builtin.shell: update-grub 40 | notify: change_requires_reboot 41 | failed_when: false 42 | 43 | - name: reload gdm3 44 | ansible.builtin.shell: dpkg-reconfigure gdm3 45 | failed_when: false 46 | 47 | - name: restart postfix 48 | ansible.builtin.systemd: 49 | name: postfix 50 | state: restarted 51 | 52 | - name: restart exim4 53 | ansible.builtin.systemd: 54 | name: exim4 55 | state: restarted 56 | 57 | - name: restart timeservice 58 | ansible.builtin.systemd: 59 | name: "{{ ubtu20cis_time_sync_tool }}" 60 | state: restarted 61 | # --------------- 62 | # --------------- 63 | # This is not a control however using the iptables module only writes to memory 64 | # if a reboot occurs that means changes can revert. This task will make the 65 | # above iptables settings permanent 66 | # 67 | # This is also idempotent as a handler 68 | # --------------- 69 | # --------------- 70 | 71 | - name: persistent ip4tables 72 | ansible.builtin.shell: bash -c "iptables-save > /etc/iptables/rules.v4" 73 | changed_when: ubtu20cis_iptables_save.rc == 0 74 | failed_when: ubtu20cis_iptables_save.rc > 0 75 | register: ubtu20cis_iptables_save 76 | 77 | - name: persistent ip6tables 78 | ansible.builtin.shell: bash -c "ip6tables-save > /etc/iptables/rules.v6" 79 | changed_when: ubtu20cis_ip6tables_save.rc == 0 80 | failed_when: ubtu20cis_ip6tables_save.rc > 0 81 | register: ubtu20cis_ip6tables_save 82 | 83 | ######## 84 | 85 | - name: sysctl flush ipv4 route table 86 | ansible.posix.sysctl: 87 | name: net.ipv4.route.flush 88 | value: '1' 89 | sysctl_set: true 90 | when: ansible_virtualization_type != "docker" 91 | 92 | - name: reload ufw 93 | community.general.ufw: 94 | state: reloaded 95 | 96 | - name: sysctl flush ipv6 route table 97 | ansible.posix.sysctl: 98 | name: net.ipv6.route.flush 99 | value: '1' 100 | sysctl_set: true 101 | when: ansible_virtualization_type != "docker" 102 | 103 | - name: restart auditd 104 | ansible.builtin.service: 105 | name: auditd 106 | state: restarted 107 | when: 108 | - not ubtu20cis_skip_for_travis 109 | tags: 110 | - skip_ansible_lint 111 | 112 | - name: restart syslog service 113 | ansible.builtin.systemd: 114 | name: "{{ ubtu20cis_syslog_service }}" 115 | state: restarted 116 | 117 | - name: restart journal-upload 118 | ansible.builtin.systemd: 119 | name: systemd-journal-upload 120 | state: restarted 121 | 122 | - name: restart sshd 123 | ansible.builtin.systemd: 124 | name: sshd 125 | state: restarted 126 | 127 | - name: reload gdm 128 | ansible.builtin.shell: dpkg-reconfigure gdm3 129 | 130 | - name: change_requires_reboot 131 | ansible.builtin.set_fact: 132 | change_requires_reboot: true 133 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | galaxy_info: 4 | author: "George Nalen, Mark Bolwell, and DFed" 5 | description: "Apply the Ubuntu 20 CIS benmarks" 6 | company: "MindPoint Group" 7 | license: MIT 8 | role_name: ubuntu20_cis 9 | namespace: mindpointgroup 10 | min_ansible_version: 2.10.1 11 | platforms: 12 | - name: Ubuntu 13 | versions: 14 | - focal 15 | galaxy_tags: 16 | - system 17 | - security 18 | - cis 19 | - hardening 20 | - benchmark 21 | - compliance 22 | - complianceascode 23 | - ubuntu20 24 | collections: 25 | - community.general 26 | - community.crypto 27 | - ansible.posix 28 | 29 | dependencies: [] 30 | -------------------------------------------------------------------------------- /site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: all 4 | become: true 5 | 6 | roles: 7 | 8 | - role: "{{ playbook_dir }}" 9 | -------------------------------------------------------------------------------- /tasks/LE_audit_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Pre Audit Setup | Set audit package name 4 | block: 5 | - name: Pre Audit Setup | Set audit package name | 64bit 6 | ansible.builtin.set_fact: 7 | audit_pkg_arch_name: AMD64 8 | when: ansible_facts.machine == "x86_64" 9 | 10 | - name: Pre Audit Setup | Set audit package name | ARM64 11 | ansible.builtin.set_fact: 12 | audit_pkg_arch_name: ARM64 13 | when: ansible_facts.machine == "aarch64" 14 | 15 | - name: Pre Audit Setup | Download audit binary 16 | ansible.builtin.get_url: 17 | url: "{{ audit_bin_url }}{{ audit_pkg_arch_name }}" 18 | dest: "{{ audit_bin }}" 19 | owner: root 20 | group: root 21 | checksum: "{{ audit_bin_version[audit_pkg_arch_name + '_checksum'] }}" 22 | mode: '0555' 23 | when: 24 | - get_audit_binary_method == 'download' 25 | 26 | - name: Pre Audit Setup | Copy audit binary 27 | ansible.builtin.copy: 28 | src: "{{ audit_bin_copy_location }}" 29 | dest: "{{ audit_bin }}" 30 | mode: '0555' 31 | owner: root 32 | group: root 33 | when: 34 | - get_audit_binary_method == 'copy' 35 | -------------------------------------------------------------------------------- /tasks/audit_only.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Audit_Only | Create local Directories for hosts 4 | ansible.builtin.file: 5 | mode: '0755' 6 | path: "{{ audit_capture_files_dir }}/{{ inventory_hostname }}" 7 | recurse: true 8 | state: directory 9 | when: fetch_audit_files 10 | delegate_to: localhost 11 | become: false 12 | 13 | - name: Audit_only | Get audits from systems and put in group dir 14 | ansible.builtin.fetch: 15 | dest: "{{ audit_capture_files_dir }}/{{ inventory_hostname }}/" 16 | flat: true 17 | mode: '0644' 18 | src: "{{ pre_audit_outfile }}" 19 | when: fetch_audit_files 20 | 21 | - name: Audit_only | Show Audit Summary 22 | when: 23 | - audit_only 24 | ansible.builtin.debug: 25 | msg: "{{ audit_results.split('\n') }}" 26 | 27 | - name: Audit_only | Stop Playbook Audit Only selected 28 | when: 29 | - audit_only 30 | ansible.builtin.meta: end_play 31 | -------------------------------------------------------------------------------- /tasks/fetch_audit_output.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Stage to copy audit output to a centralised location 4 | 5 | - name: "FETCH_AUDIT_FILES | Fetch files and copy to controller" 6 | ansible.builtin.fetch: 7 | src: "{{ item }}" 8 | dest: "{{ audit_output_destination }}" 9 | flat: true 10 | failed_when: false 11 | register: discovered_audit_fetch_state 12 | loop: 13 | - "{{ pre_audit_outfile }}" 14 | - "{{ post_audit_outfile }}" 15 | become: false 16 | when: audit_output_collection_method == "fetch" 17 | 18 | # Added this option for continuity but could be changed by adjusting the variable audit_conf_dest 19 | # Allowing backup to one location 20 | - name: "FETCH_AUDIT_FILES | Copy files to location available to managed node" 21 | ansible.builtin.copy: 22 | src: "{{ item }}" 23 | dest: "{{ audit_output_destination }}" 24 | mode: 'u-x,go-wx' 25 | flat: true 26 | failed_when: false 27 | register: discovered_audit_fetch_copy_state 28 | loop: 29 | - pre_audit_outfile 30 | - post_audit_outfile 31 | when: audit_output_collection_method == "copy" 32 | 33 | - name: "FETCH_AUDIT_FILES | Fetch files and copy to controller | Warning if issues with fetch_audit_files" 34 | block: 35 | - name: "FETCH_AUDIT_FILES | Fetch files and copy to controller | Warning if issues with fetch_audit_files" 36 | ansible.builtin.debug: 37 | msg: "Warning!! Unable to write to localhost {{ audit_output_destination }} for audit file copy" 38 | 39 | - name: "FETCH_AUDIT_FILES | Fetch files and copy to controller | Warning if issues with fetch_audit_files" 40 | vars: 41 | warn_control_id: "FETCH_AUDIT_FILES" 42 | ansible.builtin.import_tasks: 43 | file: warning_facts.yml 44 | when: 45 | - (discovered_audit_fetch_state is defined and not discovered_audit_fetch_state.changed) or 46 | (discovered_audit_copy_state is defined and not discovered_audit_copy_state.changed) 47 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Check OS version and family 4 | ansible.builtin.assert: 5 | that: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version is version_compare('20', '==') 6 | msg: "This role can only be run against Ubuntu 20. {{ ansible_distribution }} {{ ansible_distribution_major_version }} is not supported." 7 | tags: 8 | - always 9 | 10 | - name: Check ansible version 11 | ansible.builtin.assert: 12 | that: ansible_version.full is version_compare(min_ansible_version, '>=') 13 | fail_msg: "You must use Ansible {{ min_ansible_version }} or greater" 14 | success_msg: "This role is running a supported version of ansible {{ ansible_version.full }} >= {{ min_ansible_version }}" 15 | tags: 16 | - always 17 | 18 | - name: Ensure grub password has been changed 19 | ansible.builtin.assert: 20 | that: 21 | - '"ChangeMe" not in ubtu20cis_bootloader_password_hash' 22 | - '"grub.pbkdf2.sha512.1000" in ubtu20cis_bootloader_password_hash' 23 | fail_msg: "The default grub password has not been changed" 24 | when: 25 | - ubtu20cis_set_grub_user_password 26 | - ubtu20cis_rule_1_4_2 27 | 28 | - name: Ensure root password has been changed 29 | ansible.builtin.assert: 30 | that: 31 | - '"$6$this_is_needs_to_be_changed" not in ubtu20cis_root_pw' 32 | - '"$6$" in ubtu20cis_root_pw' 33 | fail_msg: "The root password set does not pass checks" 34 | when: 35 | - ubtu20cis_set_root_password 36 | - ubtu20cis_rule_1_4_3 37 | 38 | # This control should always run as this can pass on unintended issues. 39 | - name: "Check password set for connecting user" 40 | block: 41 | - name: Capture current password state of connecting user" 42 | ansible.builtin.shell: "grep {{ ansible_env.SUDO_USER }} /etc/shadow | awk -F: '{print $2}'" 43 | changed_when: false 44 | failed_when: false 45 | check_mode: false 46 | register: ansible_user_password_set 47 | 48 | - name: "Assert that password set for {{ ansible_env.SUDO_USER }} and account not locked" 49 | ansible.builtin.assert: 50 | that: ansible_user_password_set.stdout | length != 0 and ansible_user_password_set.stdout != "!!" 51 | fail_msg: "You have {{ sudo_password_rule }} enabled but the user = {{ ansible_env.SUDO_USER }} has no password set - It can break access" 52 | success_msg: "You have a password set for sudo user {{ ansible_env.SUDO_USER }}" 53 | vars: 54 | sudo_password_rule: ubtu20cis_rule_4_3_4 # pragma: allowlist secret 55 | when: 56 | - ubtu20cis_rule_4_3_4 57 | - ansible_env.SUDO_USER is defined 58 | tags: 59 | - always 60 | 61 | - name: Setup rules if container 62 | block: 63 | - name: Discover and set container variable if required 64 | ansible.builtin.set_fact: 65 | system_is_container: true 66 | 67 | - name: Load variable for container 68 | ansible.builtin.include_vars: 69 | file: "{{ container_vars_file }}" 70 | 71 | - name: Output if discovered is a container 72 | ansible.builtin.debug: 73 | msg: system has been discovered as a container 74 | when: 75 | - system_is_container 76 | when: 77 | - ansible_connection == 'docker' or 78 | ansible_virtualization_type in ["docker", "lxc", "openvz", "podman", "container"] 79 | tags: 80 | - container_discovery 81 | - always 82 | 83 | - name: Import preliminary tasks 84 | ansible.builtin.import_tasks: 85 | file: prelim.yml 86 | tags: 87 | - prelim_tasks 88 | - run_audit 89 | 90 | - name: Include audit specific variables 91 | ansible.builtin.include_vars: audit.yml 92 | when: 93 | - run_audit or audit_only 94 | - setup_audit 95 | tags: 96 | - setup_audit 97 | - run_audit 98 | 99 | - name: Include pre-remediation audit tasks 100 | ansible.builtin.import_tasks: pre_remediation_audit.yml 101 | when: 102 | - run_audit or audit_only 103 | - setup_audit 104 | tags: 105 | - run_audit 106 | 107 | - name: Run parse /etc/passwd 108 | ansible.builtin.import_tasks: 109 | file: parse_etc_password.yml 110 | when: 111 | - ubtu20cis_section5_patch or 112 | ubtu20cis_section6_patch 113 | 114 | - name: Gather the package facts 115 | ansible.builtin.package_facts: 116 | manager: auto 117 | tags: 118 | - always 119 | 120 | - name: Include section 1 patches 121 | ansible.builtin.import_tasks: 122 | file: section_1/main.yml 123 | when: 124 | - ubtu20cis_section1_patch 125 | tags: 126 | - section1 127 | 128 | - name: Include section 2 patches 129 | ansible.builtin.import_tasks: 130 | file: section_2/main.yml 131 | when: 132 | - ubtu20cis_section2_patch 133 | tags: 134 | - section2 135 | 136 | - name: Include section 3 patches 137 | ansible.builtin.import_tasks: 138 | file: section_3/main.yml 139 | when: 140 | - ubtu20cis_section3_patch 141 | tags: 142 | - section3 143 | 144 | - name: Include section 4 patches 145 | ansible.builtin.import_tasks: 146 | file: section_4/main.yml 147 | when: 148 | - ubtu20cis_section4_patch 149 | tags: 150 | - section4 151 | 152 | - name: Include section 5 patches 153 | ansible.builtin.import_tasks: 154 | file: section_5/main.yml 155 | when: 156 | - ubtu20cis_section5_patch 157 | tags: 158 | - section5 159 | 160 | - name: Include section 6 patches 161 | ansible.builtin.import_tasks: 162 | file: section_6/main.yml 163 | when: 164 | - ubtu20cis_section6_patch 165 | tags: 166 | - section6 167 | 168 | - name: flush handlers 169 | ansible.builtin.meta: flush_handlers 170 | 171 | - name: run post remediation tasks 172 | ansible.builtin.import_tasks: 173 | file: post.yml 174 | tags: 175 | - post_tasks 176 | - always 177 | 178 | - name: Run post audit 179 | ansible.builtin.import_tasks: 180 | file: post_remediation_audit.yml 181 | when: 182 | - run_audit 183 | 184 | - name: Add ansible file showing Benchmark and levels applied 185 | block: 186 | - name: Create ansible facts directory 187 | ansible.builtin.file: 188 | path: "{{ ansible_facts_path }}" 189 | state: directory 190 | owner: root 191 | group: root 192 | mode: 'u=rwx,go=rx' 193 | 194 | - name: Create ansible facts file 195 | ansible.builtin.template: 196 | src: etc/ansible/compliance_facts.j2 197 | dest: "{{ ansible_facts_path }}/compliance_facts.fact" 198 | owner: root 199 | group: root 200 | mode: "u-x,go-wx" 201 | when: create_benchmark_facts 202 | tags: 203 | - always 204 | - benchmark 205 | 206 | - name: Fetch audit files 207 | ansible.builtin.import_tasks: 208 | file: fetch_audit_output.yml 209 | when: 210 | - fetch_audit_output 211 | - run_audit 212 | tags: always 213 | 214 | - name: Show Audit Summary 215 | ansible.builtin.debug: 216 | msg: "{{ audit_results.split('\n') }}" 217 | when: 218 | - run_audit 219 | tags: 220 | - run_audit 221 | 222 | - name: Output Warning count and control IDs affected 223 | ansible.builtin.debug: 224 | msg: "You have {{ warn_count }} warning(s) that require investigating that are related to the following benchmark ID(s) {{ warn_control_list }}" 225 | tags: 226 | - always 227 | -------------------------------------------------------------------------------- /tasks/parse_etc_password.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "PRELIM | {{ ubtu20cis_passwd_tasks }} | Parse /etc/passwd" 3 | block: 4 | - name: "PRELIM | {{ ubtu20cis_passwd_tasks }} | Parse /etc/passwd" 5 | ansible.builtin.shell: cat /etc/passwd 6 | changed_when: false 7 | check_mode: false 8 | register: ubtu20cis_passwd_file_audit 9 | 10 | - name: "PRELIM | {{ ubtu20cis_passwd_tasks }} | Split passwd entries" 11 | ansible.builtin.set_fact: 12 | ubtu20cis_passwd: "{{ ubtu20cis_passwd_file_audit.stdout_lines | map('regex_replace', ld_passwd_regex, ld_passwd_yaml) | map('from_yaml') | list }}" 13 | vars: 14 | ld_passwd_regex: >- 15 | ^(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*) 16 | ld_passwd_yaml: | 17 | id: >-4 18 | \g 19 | password: >-4 20 | \g 21 | uid: \g 22 | gid: \g 23 | gecos: >-4 24 | \g 25 | dir: >-4 26 | \g 27 | shell: >-4 28 | \g 29 | tags: 30 | - always 31 | -------------------------------------------------------------------------------- /tasks/post.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Post tasks 3 | 4 | - name: POST | flush handlers 5 | ansible.builtin.meta: flush_handlers 6 | 7 | - name: POST | reboot system if changes require it and not skipped 8 | block: 9 | - name: POST | Reboot system if changes require it and not skipped 10 | ansible.builtin.reboot: 11 | when: 12 | - change_requires_reboot 13 | - not skip_reboot 14 | 15 | - name: POST | Warning a reboot required but skip option set 16 | ansible.builtin.debug: 17 | msg: "Warning!! changes have been made that require a reboot to be implemented but skip reboot was set - Can affect compliance check results" 18 | changed_when: true 19 | when: 20 | - change_requires_reboot 21 | - skip_reboot 22 | 23 | - name: "POST | Warning a reboot required but skip option set | warning count" 24 | ansible.builtin.import_tasks: 25 | file: warning_facts.yml 26 | when: 27 | - change_requires_reboot 28 | - skip_reboot 29 | vars: 30 | warn_control_id: Reboot_required 31 | tags: 32 | - always 33 | 34 | - name: If Warning count is 0 set fact 35 | ansible.builtin.set_fact: 36 | control_number: "Congratulation None Found" 37 | when: 38 | - warn_count == '0' 39 | -------------------------------------------------------------------------------- /tasks/post_remediation_audit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Post Audit | Run post_remediation {{ benchmark }} audit 4 | ansible.builtin.shell: "{{ audit_conf_dir }}/run_audit.sh -v {{ audit_vars_path }} -f {{ audit_format }} -o {{ post_audit_outfile }} -g \"{{ group_names }}\"" 5 | changed_when: true 6 | environment: 7 | AUDIT_BIN: "{{ audit_bin }}" 8 | AUDIT_CONTENT_LOCATION: "{{ audit_conf_dest | default('/opt') }}" 9 | AUDIT_FILE: goss.yml 10 | 11 | - name: Post Audit | ensure audit files readable by users 12 | ansible.builtin.file: 13 | path: "{{ item }}" 14 | mode: '0644' 15 | state: file 16 | loop: 17 | - "{{ post_audit_outfile }}" 18 | - "{{ pre_audit_outfile }}" 19 | 20 | - name: Post Audit | Capture audit data if json format 21 | when: 22 | - audit_format == "json" 23 | block: 24 | - name: capture data {{ post_audit_outfile }} 25 | ansible.builtin.shell: grep -E '"summary-line.*Count:.*Failed' "{{ post_audit_outfile }}" | cut -d'"' -f4 26 | changed_when: false 27 | register: post_audit 28 | 29 | - name: Capture post-audit result 30 | ansible.builtin.set_fact: 31 | post_audit_summary: "{{ post_audit.stdout }}" 32 | vars: 33 | summary: summary."summary-line" 34 | 35 | - name: Post Audit | Capture audit data if documentation format 36 | when: 37 | - audit_format == "documentation" 38 | block: 39 | - name: Post Audit | capture data {{ post_audit_outfile }} 40 | ansible.builtin.shell: tail -2 "{{ pre_audit_outfile }}" | tac | tr '\n' ' ' 41 | register: post_audit 42 | changed_when: false 43 | 44 | - name: Post Audit | Capture post-audit result 45 | ansible.builtin.set_fact: 46 | post_audit_summary: "{{ post_audit.stdout_lines }}" 47 | -------------------------------------------------------------------------------- /tasks/pre_remediation_audit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Pre Audit Setup | Setup the LE audit 4 | when: 5 | - setup_audit 6 | tags: 7 | - setup_audit 8 | ansible.builtin.include_tasks: 9 | file: LE_audit_setup.yml 10 | 11 | - name: Pre Audit Setup | Ensure {{ audit_conf_dir }} exists 12 | ansible.builtin.file: 13 | path: "{{ audit_conf_dir }}" 14 | state: directory 15 | mode: '0755' 16 | 17 | - name: Pre Audit Setup | If using git for content set up 18 | when: 19 | - audit_content == 'git' 20 | block: 21 | - name: Pre Audit Setup | Install git 22 | ansible.builtin.package: 23 | name: git 24 | state: present 25 | 26 | - name: Pre Audit Setup | Retrieve audit content files from git 27 | ansible.builtin.git: 28 | repo: "{{ audit_file_git }}" 29 | dest: "{{ audit_conf_dir }}" 30 | version: "{{ audit_git_version }}" 31 | 32 | - name: Pre Audit Setup | Copy to audit content files to server 33 | when: 34 | - audit_content == 'copy' 35 | ansible.builtin.copy: 36 | src: "{{ audit_conf_source }}" 37 | dest: "{{ audit_conf_dest }}" 38 | mode: preserve 39 | 40 | - name: Pre Audit Setup | Unarchive audit content files on server 41 | when: 42 | - audit_content == 'archive' 43 | ansible.builtin.unarchive: 44 | src: "{{ audit_conf_source }}" 45 | dest: "{{ audit_conf_dest }}" 46 | 47 | - name: Pre Audit Setup | Get audit content from url 48 | when: 49 | - audit_content == 'get_url' 50 | ansible.builtin.unarchive: 51 | src: "{{ audit_conf_source }}" 52 | dest: "{{ audit_conf_dest }}/{{ benchmark }}-Audit" 53 | remote_src: "{{ ( audit_conf_source is contains ('http'))| ternary(true, false ) }}" 54 | extra_opts: "{{ (audit_conf_source is contains ('github')) | ternary('--strip-components=1', [] ) }}" 55 | 56 | - name: Pre Audit Setup | Check Goss is available 57 | when: 58 | - run_audit 59 | block: 60 | - name: Pre Audit Setup | Check for goss file 61 | ansible.builtin.stat: 62 | path: "{{ audit_bin }}" 63 | register: goss_available 64 | 65 | - name: Pre Audit Setup | If audit ensure goss is available 66 | when: 67 | - not goss_available.stat.exists 68 | ansible.builtin.assert: 69 | msg: "Audit has been selected: unable to find goss binary at {{ audit_bin }}" 70 | 71 | - name: Pre Audit Setup | Copy ansible default vars values to test audit 72 | tags: 73 | - goss_template 74 | - run_audit 75 | when: 76 | - run_audit 77 | ansible.builtin.template: 78 | src: ansible_vars_goss.yml.j2 79 | dest: "{{ audit_vars_path }}" 80 | mode: '0600' 81 | 82 | - name: Pre Audit | Run pre_remediation {{ benchmark }} audit 83 | ansible.builtin.shell: "{{ audit_conf_dir }}/run_audit.sh -v {{ audit_vars_path }} -f {{ audit_format }} -o {{ pre_audit_outfile }} -g \"{{ group_names }}\"" 84 | changed_when: true 85 | environment: 86 | AUDIT_BIN: "{{ audit_bin }}" 87 | AUDIT_CONTENT_LOCATION: "{{ audit_conf_dest | default('/opt') }}" 88 | AUDIT_FILE: goss.yml 89 | 90 | - name: Pre Audit | Capture audit data if json format 91 | when: 92 | - audit_format == "json" 93 | block: 94 | - name: Pre Audit | Capture data {{ pre_audit_outfile }} 95 | ansible.builtin.shell: grep -E '\"summary-line.*Count:.*Failed' "{{ pre_audit_outfile }}" | cut -d'"' -f4 96 | register: pre_audit 97 | changed_when: false 98 | 99 | - name: Pre Audit | Capture pre-audit result 100 | ansible.builtin.set_fact: 101 | pre_audit_summary: "{{ pre_audit.stdout }}" 102 | 103 | - name: Pre Audit | Capture audit data if documentation format 104 | when: 105 | - audit_format == "documentation" 106 | block: 107 | - name: Pre Audit | Capture data {{ pre_audit_outfile }} | documentation format 108 | ansible.builtin.shell: tail -2 "{{ pre_audit_outfile }}" | tac | tr '\n' ' ' 109 | register: pre_audit 110 | changed_when: false 111 | 112 | - name: Pre Audit | Capture pre-audit result | documentation format 113 | ansible.builtin.set_fact: 114 | pre_audit_summary: "{{ pre_audit.stdout }}" 115 | 116 | - name: Audit_Only | Run Audit Only 117 | when: 118 | - audit_only 119 | ansible.builtin.import_tasks: audit_only.yml 120 | -------------------------------------------------------------------------------- /tasks/prelim.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Gather the package facts 4 | ansible.builtin.package_facts: 5 | manager: auto 6 | tags: 7 | - always 8 | 9 | - name: "PRELIM | Section 1.1 | Create list of mount points" 10 | ansible.builtin.set_fact: 11 | mount_names: "{{ ansible_mounts | map(attribute='mount') | list }}" 12 | tags: 13 | - always 14 | 15 | - name: PRELIM | Capture tmp mount type | discover mount tmp type 16 | block: 17 | - name: PRELIM | Capture tmp mount type | discover mount tmp type 18 | ansible.builtin.shell: systemctl is-enabled tmp.mount 19 | register: discover_tmp_mnt_type 20 | changed_when: false 21 | failed_when: discover_tmp_mnt_type.rc not in [ 0, 1 ] 22 | 23 | - name: PRELIM | Capture tmp mount type | Set to expected_tmp_mnt variable 24 | ansible.builtin.set_fact: 25 | tmp_mnt_type: "{{ expected_tmp_mnt }}" 26 | when: "'generated' in discover_tmp_mnt_type.stdout" 27 | 28 | - name: PRELIM | Capture tmp mount type | Set systemd service 29 | ansible.builtin.set_fact: 30 | tmp_mnt_type: tmp_systemd 31 | when: "'generated' not in discover_tmp_mnt_type.stdout" 32 | when: 33 | - "'/tmp' in mount_names" 34 | - ubtu20cis_rule_1_1_2_1 or 35 | ubtu20cis_rule_1_1_2_2 or 36 | ubtu20cis_rule_1_1_2_3 or 37 | ubtu20cis_rule_1_1_2_4 38 | tags: 39 | - always 40 | 41 | - name: "PRELIM | register if snap being used" 42 | ansible.builtin.shell: df -h | grep -wc "/snap" 43 | changed_when: false 44 | failed_when: snap_pkg_mgr.rc not in [ 0, 1 ] 45 | register: snap_pkg_mgr 46 | tags: 47 | - rule_1.1.1.6 48 | - always 49 | 50 | - name: "PRELIM | Run apt update" 51 | ansible.builtin.package: 52 | update_cache: true 53 | when: 54 | - ubtu20cis_rule_1_2_1 or 55 | ubtu20cis_rule_1_3_1 56 | tags: 57 | - rule_1.3.1 58 | - rule_1.2.1 59 | - always 60 | 61 | - name: "PRELIM | Check for autofs service" 62 | ansible.builtin.shell: "systemctl show autofs | grep LoadState | cut -d = -f 2" 63 | register: ubtu20cis_autofs_service_status 64 | changed_when: false 65 | check_mode: false 66 | when: 67 | - ubtu20cis_rule_1_1_9 68 | tags: 69 | - rule_1.1.9 70 | - section1 71 | - always 72 | 73 | - name: "PRELIM | Check for avahi-daemon service" 74 | ansible.builtin.shell: "systemctl show avahi-daemon | grep LoadState | cut -d = -f 2" 75 | register: avahi_service_status 76 | changed_when: false 77 | check_mode: false 78 | when: 79 | - ubtu20cis_rule_2_2_2 80 | tags: 81 | - rule_2.2.2 82 | - always 83 | 84 | - name: "PRELIM | Find wireless network devices" 85 | ansible.builtin.shell: find /sys/class/net/*/wireless | awk -F'/' awk '{print $5}' 86 | failed_when: wireless_interfaces.rc not in [ 0, 2 ] 87 | changed_when: false 88 | register: wireless_interfaces 89 | when: 90 | - ubtu20cis_rule_3_1_2 91 | tags: 92 | - rule_3.1.2 93 | - always 94 | 95 | - name: "PRELIM | PATCH | Ensure auditd is installed" 96 | block: 97 | - name: "PRELIM | PATCH | Ensure auditd is installed" 98 | ansible.builtin.package: 99 | name: ['auditd', 'audispd-plugins'] 100 | state: present 101 | 102 | - name: "PRELIM | 5.2.4.5 | Audit conf and rules files | list files" 103 | ansible.builtin.find: 104 | path: /etc/audit/ 105 | file_type: file 106 | recurse: true 107 | patterns: '*.conf,*.rules' 108 | register: auditd_conf_files 109 | when: 110 | - ubtu20cis_rule_5_2_1_1 111 | - "'auditd' not in ansible_facts.packages or 112 | 'auditd-plugins' not in ansible_facts.packages" 113 | - ubtu20cis_rule_5_2_4_5 or 114 | ubtu20cis_rule_5_2_4_6 or 115 | ubtu20cis_rule_5_2_4_7 116 | tags: 117 | - level2-server 118 | - level2-workstation 119 | - patch 120 | - auditd 121 | - always 122 | 123 | - name: "PRELIM | Discover Interactive UID MIN and MIN from logins.def" 124 | block: 125 | - name: "PRELIM | Capture UID_MIN information from logins.def" 126 | ansible.builtin.shell: grep -w "^UID_MIN" /etc/login.defs | awk '{print $NF}' 127 | changed_when: false 128 | register: uid_min_id 129 | 130 | - name: "PRELIM | Capture UID_MAX information from logins.def" 131 | ansible.builtin.shell: grep -w "^UID_MAX" /etc/login.defs | awk '{print $NF}' 132 | changed_when: false 133 | register: uid_max_id 134 | 135 | - name: "PRELIM | Capture GID_MIN information from logins.def" 136 | ansible.builtin.shell: grep -w "^GID_MIN" /etc/login.defs | awk '{print $NF}' 137 | changed_when: false 138 | register: gid_min_id 139 | 140 | - name: "PRELIM | set_facts for interactive uid/gid" 141 | ansible.builtin.set_fact: 142 | min_int_uid: "{{ uid_min_id.stdout }}" 143 | max_int_uid: "{{ uid_max_id.stdout }}" 144 | min_int_gid: "{{ gid_min_id.stdout }}" 145 | when: 146 | - not discover_int_uid 147 | tags: 148 | - always 149 | 150 | - name: "PRELIM | Interactive User accounts" 151 | ansible.builtin.shell: 'cat /etc/passwd | cut -d: -f5,6,7 | grep -Ev ":(/usr|)/(s|)bin/nologin|/root|:/bin:" | cut -d: -f2' 152 | changed_when: false 153 | register: interactive_users_home 154 | tags: 155 | - always 156 | 157 | - name: "PRELIM | Install Network-Manager" 158 | ansible.builtin.package: 159 | name: network-manager 160 | state: present 161 | when: 162 | - wireless_interfaces.stdout is defined 163 | - wireless_interfaces.stdout | length > 0 164 | - ubtu20cis_install_network_manager 165 | - ubtu20cis_rule_3_1_2 166 | - not ubtu20cis_system_is_container 167 | tags: 168 | - rule_3.1.2 169 | - always 170 | 171 | - name: "PRELIM | 4.3.4 | Find all sudoers files." 172 | ansible.builtin.shell: "find /etc/sudoers /etc/sudoers.d/ -type f ! -name '*~' ! -name '*.*'" 173 | changed_when: false 174 | failed_when: false 175 | check_mode: false 176 | register: ubtu20cis_sudoers_files 177 | when: 178 | - ubtu20cis_rule_4_3_4 or 179 | ubtu20cis_rule_4_3_5 180 | tags: 181 | - always 182 | 183 | - name: "PRELIM | Install ACL" 184 | ansible.builtin.package: 185 | name: acl 186 | state: present 187 | when: 188 | - ubtu20cis_rule_6_2_6 189 | tags: 190 | - rule_6.2.6 191 | - always 192 | 193 | - name: "PRELIM | List users accounts" 194 | ansible.builtin.shell: "awk -F: '{print $1}' /etc/passwd" 195 | changed_when: false 196 | check_mode: false 197 | register: ubtu20cis_users 198 | when: 199 | - ubtu20cis_rule_6_2_8 or 200 | ubtu20cis_rule_6_2_9 or 201 | ubtu20cis_rule_6_2_10 or 202 | ubtu20cis_rule_6_2_12 203 | tags: 204 | - always 205 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.10.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.10 | PATCH | Disable USB Storage" 4 | block: 5 | - name: "1.1.10 | PATCH | Disable USB Storage | Set modprobe config" 6 | ansible.builtin.lineinfile: 7 | path: /etc/modprobe.d/usb_storage.conf 8 | regexp: '^install usb-storage' 9 | line: 'install usb-storage /bin/true' 10 | create: true 11 | 12 | - name: "1.1.10 | PATCH | Disable USB Storage | Remove usb-storage module" 13 | community.general.modprobe: 14 | name: usb-storage 15 | state: absent 16 | when: ansible_connection != 'docker' 17 | 18 | - name: "1.1.10 | PATCH | Disable USB Storage | blacklist" 19 | ansible.builtin.lineinfile: 20 | path: /etc/modprobe.d/blacklist.conf 21 | regexp: '^blacklist usb-storage' 22 | line: 'blacklist usb-storage' 23 | create: true 24 | when: 25 | - ubtu20cis_rule_1_1_10 26 | - not ubtu20cis_allow_usb_storage 27 | tags: 28 | - level1-server 29 | - level2-workstation 30 | - automated 31 | - patch 32 | - rule_1.1.10 33 | - usb_storage 34 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.2.1 | AUDIT | Ensure /tmp is a separate partition" 4 | block: 5 | - name: "1.1.2.1 | AUDIT | Ensure /tmp is a separate partition | Absent" 6 | ansible.builtin.debug: 7 | msg: "Warning!! {{ required_mount }} doesn't exist. This is a manual task" 8 | 9 | - name: "1.1.2.1 | WARN | Ensure /tmp is a separate partition | warn_count" 10 | ansible.builtin.import_tasks: 11 | file: warning_facts.yml 12 | vars: 13 | warn_control_id: '1.1.2.1' 14 | required_mount: '/tmp' 15 | when: 16 | - required_mount not in mount_names 17 | - ubtu20cis_rule_1_1_2_1 18 | tags: 19 | - level1-server 20 | - level1-workstation 21 | - audit 22 | - mounts 23 | - rule_1.1.2.1 24 | - tmp 25 | 26 | - name: | 27 | "1.1.2.2 | PATCH | Ensure nodev option set on /tmp partition | tmp_systemd" 28 | "1.1.2.3 | PATCH | Ensure noexec option set on /tmp partition | tmp_systemd" 29 | "1.1.2.4 | PATCH | Ensure nosuid option set on /tmp partition | tmp_systemd" 30 | ansible.builtin.template: 31 | src: etc/systemd/system/tmp.mount.j2 32 | dest: /etc/systemd/system/tmp.mount 33 | owner: root 34 | group: root 35 | mode: 0644 36 | notify: Remount tmp 37 | with_items: 38 | - "{{ ansible_mounts }}" 39 | loop_control: 40 | label: "{{ item.device }}" 41 | when: 42 | - item.mount == "/tmp" 43 | - tmp_mnt_type == 'tmp_systemd' 44 | - ubtu20cis_rule_1_1_2_1 or 45 | ubtu20cis_rule_1_1_2_2 or 46 | ubtu20cis_rule_1_1_2_3 or 47 | ubtu20cis_rule_1_1_2_4 48 | tags: 49 | - level1-server 50 | - level1-workstation 51 | - automated 52 | - patch 53 | - rule_1.1.2.2 54 | - rule_1.1.2.3 55 | - rule_1.1.2.4 56 | - tmp 57 | 58 | - name: | 59 | "1.1.2.2 | PATCH | Ensure nodev option set on /tmp partition | fstab" 60 | "1.1.2.3 | PATCH | Ensure noexec option set on /tmp partition | fstab" 61 | "1.1.2.4 | PATCH | Ensure nosuid option set on /tmp partition | fstab" 62 | ansible.posix.mount: 63 | path: /tmp 64 | src: "{{ item.device }}" 65 | state: present 66 | fstype: "{{ item.fstype }}" 67 | opts: defaults,{% if ubtu20cis_rule_1_1_2_2 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_2_3 %}noexec,{% endif %}{% if ubtu20cis_rule_1_1_2_4 %}nosuid{% endif %} 68 | notify: Remount tmp 69 | with_items: 70 | - "{{ ansible_mounts }}" 71 | loop_control: 72 | label: "{{ item.device }}" 73 | when: 74 | - item.mount == "/tmp" 75 | - tmp_mnt_type == 'fstab' 76 | - ubtu20cis_rule_1_1_2_1 or 77 | ubtu20cis_rule_1_1_2_2 or 78 | ubtu20cis_rule_1_1_2_3 or 79 | ubtu20cis_rule_1_1_2_4 80 | tags: 81 | - level1-server 82 | - level1-workstation 83 | - automated 84 | - patch 85 | - rule_1.1.1.2 86 | - rule_1.1.2.2 87 | - rule_1.1.2.3 88 | - rule_1.1.2.4 89 | - tmp 90 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.3.1 | AUDIT | Ensure separate partition exists for /var" 4 | block: 5 | - name: "1.1.3.1 | AUDIT | Ensure separate partition exists for /var | Absent" 6 | ansible.builtin.debug: 7 | msg: "Warning!! {{ required_mount }} doesn't exist. This is a manual task" 8 | 9 | - name: "1.1.3.1 | WARN | Ensure separate partition exists for /var | warn_count" 10 | ansible.builtin.import_tasks: 11 | file: warning_facts.yml 12 | vars: 13 | warn_control_id: '1.1.3.1' 14 | required_mount: '/var' 15 | when: 16 | - required_mount not in mount_names 17 | - ubtu20cis_rule_1_1_3_1 18 | tags: 19 | - level2-server 20 | - level2-workstation 21 | - automated 22 | - audit 23 | - rule_1.1.3.1 24 | - var 25 | 26 | - name: | 27 | "1.1.3.2 | PATCH | Ensure /var partition includes the nodev option" 28 | "1.1.3.3 | PATCH | Ensure /var partition includes the nosuid option" 29 | ansible.posix.mount: 30 | path: /var 31 | src: "{{ item.device }}" 32 | state: present 33 | fstype: "{{ item.fstype }}" 34 | opts: defaults,{% if ubtu20cis_rule_1_1_3_2 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_3_3 %}nosuid{% endif %} 35 | notify: Remount var 36 | loop: "{{ ansible_mounts }}" 37 | loop_control: 38 | label: "{{ item.device }}" 39 | when: 40 | - item.mount == "/var" 41 | - ubtu20cis_rule_1_1_3_2 or 42 | ubtu20cis_rule_1_1_3_3 43 | tags: 44 | - level1-server 45 | - level1-workstation 46 | - automated 47 | - patch 48 | - rule_1.1.3.2 49 | - rule_1.1.3.3 50 | - var 51 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.4.1 | AUDIT | Ensure separate partition exists for /var/tmp " 4 | block: 5 | - name: "1.1.4.1 | AUDIT | Ensure separate partition exists for /var/tmp | Absent" 6 | ansible.builtin.debug: 7 | msg: "Warning!! {{ required_mount }} doesn't exist. This is a manual task" 8 | 9 | - name: "1.1.4.1 | WARN | Ensure separate partition exists for /var/tmp | warn_count" 10 | ansible.builtin.import_tasks: 11 | file: warning_facts.yml 12 | vars: 13 | warn_control_id: '1.1.4.1' 14 | required_mount: '/var/tmp' 15 | when: 16 | - required_mount not in mount_names 17 | - ubtu20cis_rule_1_1_4_1 18 | tags: 19 | - level2-server 20 | - level2-workstation 21 | - automated 22 | - audit 23 | - rule_1.1.4.1 24 | - var 25 | 26 | - name: | 27 | "1.1.4.2 | PATCH | Ensure /var/tmp partition includes the nodev option" 28 | "1.1.4.3 | PATCH | Ensure /var/tmp partition includes the noexec option" 29 | "1.1.4.4 | PATCH | Ensure /var/tmp partition includes the nosuid option" 30 | ansible.posix.mount: 31 | path: /var/tmp 32 | src: "{{ item.device }}" 33 | state: present 34 | fstype: "{{ item.fstype }}" 35 | opts: defaults,{% if ubtu20cis_rule_1_1_4_2 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_4_3 %}noexec,{% endif %}{% if ubtu20cis_rule_1_1_4_4 %}nosuid{% endif %} 36 | notify: Remount var_tmp 37 | with_items: "{{ ansible_mounts }}" 38 | loop_control: 39 | label: "{{ item.device }}" 40 | when: 41 | - item.mount == "/var/tmp" 42 | - ubtu20cis_rule_1_1_4_2 or 43 | ubtu20cis_rule_1_1_4_3 or 44 | ubtu20cis_rule_1_1_4_4 45 | tags: 46 | - level1-server 47 | - level1-workstation 48 | - automated 49 | - patch 50 | - rule_1.1.4.2 51 | - rule_1.1.4.3 52 | - rule_1.1.4.4 53 | - var 54 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.5.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.5.1 | AUDIT | Ensure separate partition exists for /var/log" 4 | block: 5 | - name: "1.1.5.1 | AUDIT | Ensure separate partition exists for /var/log| Absent" 6 | ansible.builtin.debug: 7 | msg: "Warning!! {{ required_mount }} doesn't exist. This is a manual task" 8 | 9 | - name: "1.1.5.1 | WARN | Ensure separate partition exists for /var/log | warn_count" 10 | ansible.builtin.import_tasks: 11 | file: warning_facts.yml 12 | vars: 13 | warn_control_id: '1.1.5.1' 14 | required_mount: '/var/log' 15 | when: 16 | - required_mount not in mount_names 17 | - ubtu20cis_rule_1_1_5_1 18 | tags: 19 | - level2-server 20 | - level2-workstation 21 | - automated 22 | - audit 23 | - rule_1.1.5.1 24 | - varlog 25 | 26 | - name: | 27 | "1.1.5.2 | PATCH | Ensure /var/log partition includes the nodev option" 28 | "1.1.5.3 | PATCH | Ensure /var/log partition includes the noexec option" 29 | "1.1.5.4 | PATCH | Ensure /var/log partition includes the nosuid option" 30 | ansible.posix.mount: 31 | path: /var/log 32 | src: "{{ item.device }}" 33 | state: present 34 | fstype: "{{ item.fstype }}" 35 | opts: defaults,{% if ubtu20cis_rule_1_1_5_2 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_5_3 %}noexec,{% endif %}{% if ubtu20cis_rule_1_1_5_4 %}nosuid{% endif %} 36 | notify: Remount var_log 37 | loop: "{{ ansible_mounts }}" 38 | loop_control: 39 | label: "{{ item.device }}" 40 | when: 41 | - item.mount == "/var/log" 42 | - ubtu20cis_rule_1_1_5_2 or 43 | ubtu20cis_rule_1_1_5_3 or 44 | ubtu20cis_rule_1_1_5_4 45 | tags: 46 | - level1-server 47 | - level1-workstation 48 | - automated 49 | - patch 50 | - rule_1.1.5.2 51 | - rule_1.1.5.3 52 | - rule_1.1.5.4 53 | - varlog 54 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.6.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.6.1 | AUDIT | Ensure separate partition exists for /var/log/audit" 4 | block: 5 | - name: "1.1.6.1 | AUDIT | Ensure separate partition exists for /var/log/audit | Absent" 6 | ansible.builtin.debug: 7 | msg: "Warning!! {{ required_mount }} doesn't exist. This is a manual task" 8 | 9 | - name: "1.1.6.1 | WARN | Ensure separate partition exists for /var/log/audit | warn_count" 10 | ansible.builtin.import_tasks: 11 | file: warning_facts.yml 12 | vars: 13 | warn_control_id: '1.1.6.1' 14 | required_mount: '/var/log/audit' 15 | when: 16 | - required_mount not in mount_names 17 | - ubtu20cis_rule_1_1_6_1 18 | tags: 19 | - level2-server 20 | - level2-workstation 21 | - automated 22 | - audit 23 | - rule_1.1.6.1 24 | - varlogaudit 25 | 26 | - name: | 27 | "1.1.6.2 | PATCH | Ensure /var/log/audit partition includes the nodev option" 28 | "1.1.6.3 | PATCH | Ensure /var/log/audit partition includes the noexec option" 29 | "1.1.6.4 | PATCH | Ensure /var/log/audit partition includes the nosuid option" 30 | ansible.posix.mount: 31 | path: /var/log/audit 32 | src: "{{ item.device }}" 33 | state: present 34 | fstype: "{{ item.fstype }}" 35 | opts: defaults,{% if ubtu20cis_rule_1_1_6_2 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_6_3 %}noexec,{% endif %}{% if ubtu20cis_rule_1_1_6_4 %}nosuid{% endif %} 36 | notify: Remount var_log_audit 37 | loop: "{{ ansible_mounts }}" 38 | loop_control: 39 | label: "{{ item.device }}" 40 | when: 41 | - item.mount == "/var/log/audit" 42 | - ubtu20cis_rule_1_1_6_2 or 43 | ubtu20cis_rule_1_1_6_3 or 44 | ubtu20cis_rule_1_1_6_4 45 | tags: 46 | - level1-server 47 | - level1-workstation 48 | - automated 49 | - patch 50 | - rule_1.1.6.2 51 | - rule_1.1.6.3 52 | - rule_1.1.6.4 53 | - varlogaudit 54 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.7.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.7.1 | AUDIT | Ensure /home is a separate partition" 4 | block: 5 | - name: "1.1.7.1 | AUDIT | Ensure separate partition exists for /home | Absent" 6 | ansible.builtin.debug: 7 | msg: "Warning!! {{ required_mount }} doesn't exist. This is a manual task" 8 | 9 | - name: "1.1.7.1 | WARN | Ensure separate partition exists for /home | warn_count" 10 | ansible.builtin.import_tasks: 11 | file: warning_facts.yml 12 | vars: 13 | warn_control_id: '1.1.7.1' 14 | required_mount: '/home' 15 | when: 16 | - required_mount not in mount_names 17 | - ubtu20cis_rule_1_1_7_1 18 | tags: 19 | - level2-server 20 | - level2-workstation 21 | - automated 22 | - audit 23 | - rule_1.1.7.1 24 | - home 25 | 26 | - name: | 27 | "1.1.7.2 | PATCH | Ensure /home partition includes the nodev option" 28 | "1.1.7.3 | PATCH | Ensure /home partition includes the nosuid option" 29 | ansible.posix.mount: 30 | path: /home 31 | src: "{{ item.device }}" 32 | state: present 33 | fstype: "{{ item.fstype }}" 34 | opts: defaults,{% if ubtu20cis_rule_1_1_7_2 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_7_3 %}nosuid,{% endif %} 35 | notify: Remount home 36 | loop: "{{ ansible_mounts }}" 37 | loop_control: 38 | label: "{{ item.device }}" 39 | when: 40 | - item.mount == "/home" 41 | - ubtu20cis_rule_1_1_7_2 or 42 | ubtu20cis_rule_1_1_7_3 43 | tags: 44 | - level1-server 45 | - level1-workstation 46 | - automated 47 | - patch 48 | - rule_1.1.7.2 49 | - rule_1.1.7.3 50 | - home 51 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.8.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: | 4 | "1.1.8.1 | PATCH | Ensure nodev option set on /dev/shm partition" 5 | "1.1.8.2 | PATCH | Ensure noexec option set on /dev/shm partition" 6 | "1.1.8.3 | PATCH | Ensure nosuid option set on /dev/shm partition" 7 | ansible.posix.mount: 8 | path: /dev/shm 9 | src: /dev/shm 10 | fstype: tmpfs 11 | state: present 12 | opts: "defaults,{% if ubtu20cis_rule_1_1_8_1 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_8_2 %}noexec,{% endif %}{% if ubtu20cis_rule_1_1_8_3 %}nosuid{% endif %}" 13 | notify: remount dev_shm 14 | when: 15 | - ubtu20cis_rule_1_1_8_1 or 16 | ubtu20cis_rule_1_1_8_2 or 17 | ubtu20cis_rule_1_1_8_3 18 | tags: 19 | - level1-server 20 | - level1-workstation 21 | - automated 22 | - patch 23 | - rule_1.1.8.1 24 | - rule_1.1.8.2 25 | - rule_1.1.8.3 26 | - dev_shm 27 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.9.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.9 | PATCH | Disable Automounting" 4 | ansible.builtin.service: 5 | name: autofs 6 | state: stopped 7 | enabled: false 8 | when: 9 | - ubtu20cis_rule_1_1_9 10 | - ubtu20cis_autofs_service_status.stdout == "loaded" 11 | - not ubtu20cis_allow_autofs 12 | tags: 13 | - level1-server 14 | - level2-workstation 15 | - automated 16 | - patch 17 | - rule_1.1.9 18 | - automounting 19 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.2.1 | PATCH | Ensure AIDE is installed" 4 | block: 5 | - name: "1.2.1 | PATCH | Ensure AIDE is installed" 6 | ansible.builtin.package: 7 | name: ['aide', 'aide-common'] 8 | state: present 9 | update_cache: true 10 | 11 | - name: "1.2.1 | PATCH | Ensure AIDE is installed | Recapture packages" 12 | ansible.builtin.package_facts: 13 | manager: auto 14 | 15 | - name: "1.2.1 | PATCH | Ensure AIDE is installed | Configure AIDE" 16 | ansible.builtin.shell: aide init && mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db 17 | args: 18 | creates: /var/lib/aide/aide.db 19 | changed_when: false 20 | failed_when: false 21 | async: 45 22 | poll: 0 23 | when: not ansible_check_mode 24 | when: 25 | - ubtu20cis_rule_1_2_1 26 | - ubtu20cis_config_aide 27 | - "'aide' not in ansible_facts.packages or 28 | 'aide-common' not in ansible_facts.packages" 29 | tags: 30 | - level1-server 31 | - level1-workstation 32 | - automated 33 | - patch 34 | - rule_1.2.1 35 | - aide 36 | 37 | - name: "1.2.2 | PATCH | Ensure filesystem integrity is regularly checked" 38 | ansible.builtin.cron: 39 | name: Run AIDE integrity check 40 | cron_file: "{{ ubtu20cis_aide_cron['cron_file'] }}" 41 | user: "{{ ubtu20cis_aide_cron['cron_user'] }}" 42 | minute: "{{ ubtu20cis_aide_cron['aide_minute'] | default('0') }}" 43 | hour: "{{ ubtu20cis_aide_cron['aide_hour'] | default('5') }}" 44 | day: "{{ ubtu20cis_aide_cron['aide_day'] | default('*') }}" 45 | month: "{{ ubtu20cis_aide_cron['aide_month'] | default('*') }}" 46 | weekday: "{{ ubtu20cis_aide_cron['aide_weekday'] | default('*') }}" 47 | job: "{{ ubtu20cis_aide_cron['aide_job'] }}" 48 | when: 49 | - ubtu20cis_config_aide 50 | - ubtu20cis_rule_1_2_2 51 | tags: 52 | - level1-server 53 | - level1-workstation 54 | - automated 55 | - patch 56 | - rule_1.2.2 57 | - cron 58 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.3.1 | PATCH | Ensure updates, patches, and additional security software are installed" 4 | ansible.builtin.package: 5 | name: "*" 6 | state: latest 7 | when: 8 | - ubtu20cis_rule_1_3_1 9 | tags: 10 | - level1-server 11 | - level1-workstation 12 | - manual 13 | - audit 14 | - rule_1.3.1 15 | - apt 16 | 17 | - name: "1.3.2 | AUDIT | Ensure package manager repositories are configured" 18 | block: 19 | - name: "1.3.2 | AUDIT | Ensure package manager repositories are configured | Get repositories" 20 | ansible.builtin.shell: apt-cache policy 21 | changed_when: false 22 | failed_when: false 23 | check_mode: false 24 | register: ubtu20cis_1_3_2_apt_policy 25 | 26 | - name: "1.3.2 | AUDIT | Ensure package manager repositories are configured | Message out repository configs" 27 | ansible.builtin.debug: 28 | msg: 29 | - "Warning!! Below are the apt package repositories" 30 | - "Please review to make sure they conform to your sites policies" 31 | - "{{ ubtu20cis_1_3_2_apt_policy.stdout_lines }}" 32 | 33 | - name: "1.3.2 | AUDIT | Ensure package manager repositories are configured | Warn Count" 34 | ansible.builtin.import_tasks: 35 | file: warning_facts.yml 36 | vars: 37 | warn_control_id: '1.3.2' 38 | when: 39 | - ubtu20cis_rule_1_3_2 40 | tags: 41 | - level1-server 42 | - level1-workstation 43 | - manual 44 | - audit 45 | - rule_1.3.2 46 | - apt 47 | 48 | - name: "1.3.3 | AUDIT | Ensure GPG keys are configured" 49 | block: 50 | - name: "1.3.3 | AUDIT | Ensure GPG keys are configured | Get apt gpg keys" 51 | ansible.builtin.shell: apt-key list 52 | changed_when: false 53 | failed_when: false 54 | check_mode: false 55 | register: ubtu20cis_1_3_3_apt_gpgkeys 56 | 57 | - name: "1.3.3 | AUDIT | Ensure GPG keys are configured | Message out apt gpg keys" 58 | ansible.builtin.debug: 59 | msg: 60 | - "Warning!! Below are the apt gpg keys configured" 61 | - "Please review to make sure they are configured" 62 | - "in accordance with site policy" 63 | - "{{ ubtu20cis_1_3_3_apt_gpgkeys.stdout_lines }}" 64 | 65 | - name: "1.3.3 | AUDIT | Ensure GPG keys are configured | Warn Count" 66 | ansible.builtin.import_tasks: 67 | file: warning_facts.yml 68 | vars: 69 | warn_control_id: '1.3.3' 70 | when: 71 | - ubtu20cis_rule_1_3_3 72 | tags: 73 | - level1-server 74 | - level1-workstation 75 | - manual 76 | - audit 77 | - rule_1.3.3 78 | - gpg 79 | - keys 80 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.4.1 | PATCH | Ensure bootloader password is set" 4 | block: 5 | - name: "1.4.1 | PATCH | Ensure bootloader password is set | set superusers and password" 6 | ansible.builtin.lineinfile: 7 | path: "{{ ubtu20cis_grub_user_file }}" 8 | regexp: "{{ item.regexp }}" 9 | line: "{{ item.line }}" 10 | state: present 11 | insertafter: "{{ item.after | default(omit) }}" 12 | create: true 13 | loop: 14 | - { regexp: '^set superusers', line: 'set superusers="{{ ubtu20cis_grub_user }}"' } 15 | - { regexp: '^password_pbkdf2 {{ ubtu20cis_grub_user }} grub.pbkdf2.*', line: 'password_pbkdf2 {{ ubtu20cis_grub_user }} {{ ubtu20cis_bootloader_password_hash }}', after: 'set superusers="' } 16 | notify: Grub update 17 | 18 | - name: "1.4.1 | PATCH | Ensure bootloader password is set | allow unrestricted boot" 19 | ansible.builtin.lineinfile: 20 | path: "/etc/grub.d/10_linux" 21 | regexp: '(^CLASS="--class gnu-linux --class gnu --class os).*"$' 22 | line: '\g<1> --unrestricted"' 23 | backrefs: true 24 | notify: Grub update 25 | when: not ubtu20cis_ask_passwd_to_boot 26 | when: 27 | - ubtu20cis_set_boot_pass 28 | - ubtu20cis_rule_1_4_1 29 | tags: 30 | - level1-server 31 | - level1-workstation 32 | - automated 33 | - patch 34 | - rule_1.4.1 35 | - grub 36 | 37 | - name: "1.4.2 | PATCH | Ensure permissions on bootloader config are configured" 38 | block: 39 | - name: "1.4.2 | AUDIT | Ensure permissions on bootloader config are configured | Check for Grub file" 40 | ansible.builtin.stat: 41 | path: "{{ ubtu20cis_grub_file }}" 42 | check_mode: false 43 | register: ubtu20cis_1_4_2_grub_cfg_status 44 | 45 | - name: "1.4.2 | PATCH | Ensure permissions on bootloader config are configured | Set permissions" 46 | ansible.builtin.file: 47 | path: "{{ ubtu20cis_grub_file }}" 48 | owner: root 49 | group: root 50 | mode: 0600 51 | when: 52 | - ubtu20cis_1_4_2_grub_cfg_status.stat.exists 53 | - ubtu20cis_1_4_2_grub_cfg_status.stat.mode != "0400" 54 | when: 55 | - ubtu20cis_rule_1_4_2 56 | tags: 57 | - level1-server 58 | - level1-workstation 59 | - automated 60 | - patch 61 | - rule_1.4.2 62 | - grub 63 | 64 | - name: "1.4.3 | PATCH | Ensure authentication required for single user mode" 65 | ansible.builtin.user: 66 | name: "{{ ubtu20cis_grub_user }}" 67 | password: "{{ ubtu20cis_grub_user_passwd }}" 68 | when: 69 | - ubtu20cis_rule_1_4_3 70 | - ubtu20cis_set_grub_user_password 71 | tags: 72 | - level1-server 73 | - level1-workstation 74 | - automated 75 | - patch 76 | - rule_1.4.3 77 | - passwd 78 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.5.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.5.1 | PATCH | Ensure prelink is not installed" 4 | block: 5 | - name: "1.5.1 | PATCH | Ensure prelink is not installed | Restore binaries to normal" 6 | ansible.builtin.shell: prelink -ua 7 | changed_when: false 8 | failed_when: false 9 | 10 | - name: "1.5.1 | PATCH | Ensure prelink is not installed| Remove prelink package" 11 | ansible.builtin.package: 12 | name: prelink 13 | state: absent 14 | when: 15 | - ubtu20cis_rule_1_5_1 16 | tags: 17 | - level1-server 18 | - level1-workstation 19 | - automated 20 | - patch 21 | - rule_1.5.1 22 | - prelink 23 | 24 | - name: "1.5.2 | PATCH | Ensure address space layout randomization (ASLR) is enabled | Set active kernel parameter" 25 | ansible.posix.sysctl: 26 | name: kernel.randomize_va_space 27 | value: '2' 28 | when: 29 | - ubtu20cis_rule_1_5_2 30 | tags: 31 | - level1-server 32 | - level1-workstation 33 | - automated 34 | - patch 35 | - rule_1.5.2 36 | - aslr 37 | 38 | - name: "1.5.3 | PATCH | Ensure ptrace_scope is restricted | sysctl.conf" 39 | ansible.builtin.lineinfile: 40 | path: /etc/sysctl.d/60-kernel_sysctl.conf 41 | regexp: '^kernel.yama.ptrace_scope ' 42 | line: kernel.yama.ptrace_scope=1 43 | create: true 44 | owner: root 45 | group: root 46 | mode: '0644' 47 | when: 48 | - ubtu20cis_rule_1_5_3 49 | tags: 50 | - level1-server 51 | - level1-workstation 52 | - automated 53 | - patch 54 | - rule_1.5.3 55 | 56 | - name: "1.5.4 | PATCH | Ensure Automatic Error Reporting is not enabled" 57 | block: 58 | - name: "1.5.4 | PATCH | Ensure Automatic Error Reporting is not enabled | disable" 59 | ansible.builtin.lineinfile: 60 | path: /etc/default/apport 61 | regexp: ^enabled 62 | line: enabled=0 63 | create: true 64 | owner: root 65 | group: root 66 | mode: 0644 67 | 68 | - name: "1.5.4 | PATCH | Ensure Automatic Error Reporting is not enabled | stop service" 69 | ansible.builtin.systemd: 70 | name: apport 71 | state: stopped 72 | enabled: false 73 | when: 74 | - "'apport' in ansible_facts.packages" 75 | 76 | - name: "1.5.4 | PATCH | Ensure Automatic Error Reporting is not enabled | remove package" 77 | ansible.builtin.package: 78 | name: apport 79 | state: absent 80 | when: 81 | - "'apport' in ansible_facts.packages" 82 | when: 83 | - ubtu20cis_rule_1_5_4 84 | tags: 85 | - level1-server 86 | - level1-workstation 87 | - automated 88 | - patch 89 | - rule_1.5.4 90 | - apport 91 | 92 | - name: "1.5.5 | PATCH | Ensure core dumps are restricted" 93 | block: 94 | - name: "1.5.5 | PATCH | Ensure core dumps are restricted | kernel sysctl" 95 | ansible.posix.sysctl: 96 | name: fs.suid_dumpable 97 | value: '0' 98 | state: present 99 | reload: true 100 | sysctl_set: true 101 | ignoreerrors: true 102 | 103 | - name: "1.5.5 | PATCH | Ensure core dumps are restricted | security limits" 104 | ansible.builtin.lineinfile: 105 | path: /etc/security/limits.d/99_zero_core.conf 106 | regexp: '^\* hard core' 107 | line: '* hard core 0' 108 | create: true 109 | owner: root 110 | group: root 111 | mode: '0644' 112 | 113 | - name: "1.5.5 | PATCH | Ensure core dumps are restricted | sysctl.conf" 114 | ansible.builtin.lineinfile: 115 | path: /etc/sysctl.conf 116 | regexp: '^fs.suid_dumpable' 117 | line: fs.suid_dumpable=0 118 | owner: root 119 | group: root 120 | mode: '0644' 121 | 122 | - name: "1.5.5 | PATCH | Ensure core dumps are restricted | coredump.conf" 123 | ansible.builtin.lineinfile: 124 | path: /etc/systemd/coredump.conf 125 | regexp: "{{ item.regexp }}" 126 | line: "{{ item.line }}" 127 | create: true 128 | owner: root 129 | group: root 130 | mode: '0644' 131 | loop: 132 | - { regexp: '^Storage', line: 'Storage=none' } 133 | - { regexp: '^ProcessSizeMax', line: 'ProcessSizeMax=0' } 134 | when: "'systemd-coredump' in ansible_facts.packages" 135 | when: 136 | - ubtu20cis_rule_1_5_5 137 | tags: 138 | - level1-server 139 | - level1-workstation 140 | - automated 141 | - patch 142 | - rule_1.5.5 143 | - coredump 144 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.6.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "1.6.1.1 | PATCH | Ensure AppArmor is installed" 3 | ansible.builtin.package: 4 | name: ['apparmor', 'apparmor-utils'] 5 | state: present 6 | when: 7 | - ubtu20cis_rule_1_6_1_1 8 | tags: 9 | - level1-server 10 | - level1-workstation 11 | - automated 12 | - patch 13 | - rule_1.6.1.1 14 | - apparmor 15 | 16 | - name: "1.6.1.2 | PATCH | Ensure AppArmor is enabled in the bootloader configuration" 17 | block: 18 | - name: "1.6.1.2 | AUDIT | Ensure AppArmor is enabled in the bootloader configuration | Get current settings" 19 | ansible.builtin.shell: grep "GRUB_CMDLINE_LINUX=" /etc/default/grub | cut -f2 -d'"' 20 | changed_when: false 21 | failed_when: false 22 | check_mode: false 23 | register: ubtu20cis_1_6_1_2_cmdline_settings 24 | 25 | - name: "1.6.1.2 | PATCH | Ensure AppArmor is enabled in the bootloader configuration | Set apparmor settings if none exist" 26 | ansible.builtin.lineinfile: 27 | path: /etc/default/grub 28 | regexp: '^GRUB_CMDLINE_LINUX' 29 | line: 'GRUB_CMDLINE_LINUX="apparmor=1 security=apparmor {{ ubtu20cis_1_6_1_2_cmdline_settings.stdout }}"' 30 | insertafter: '^GRUB_' 31 | notify: Grub update 32 | when: 33 | - "'apparmor' not in ubtu20cis_1_6_1_2_cmdline_settings.stdout" 34 | - "'security' not in ubtu20cis_1_6_1_2_cmdline_settings.stdout" 35 | 36 | - name: "1.6.1.2 | PATCH | Ensure AppArmor is enabled in the bootloader configuration | Set apparmor settings if none exist | Replace apparmor settings when exists" 37 | ansible.builtin.replace: 38 | path: /etc/default/grub 39 | regexp: "{{ item.regexp }}" 40 | replace: "{{ item.replace }}" 41 | with_items: 42 | - { regexp: 'apparmor=[^\s"]+', replace: 'apparmor=1' } 43 | - { regexp: 'security=[^\s"]+', replace: 'security=apparmor' } 44 | when: 45 | - "'apparmor' in ubtu20cis_1_6_1_2_cmdline_settings.stdout" 46 | - "'security' in ubtu20cis_1_6_1_2_cmdline_settings.stdout" 47 | notify: Grub update 48 | when: 49 | - ubtu20cis_rule_1_6_1_2 50 | tags: 51 | - level1-server 52 | - level1-workstation 53 | - automated 54 | - patch 55 | - rule_1.6.1.2 56 | - apparmor 57 | 58 | # Any unconfined processes may need to have a profile created or activated for them and then be restarted So manual task 59 | - name: "1.6.1.3 | PATCH | Ensure all AppArmor Profiles are in enforce or complain mode" 60 | block: 61 | - name: "1.6.1.3 | AUDIT | Ensure all AppArmor Profiles are in enforce or complain mode | capture state" 62 | ansible.builtin.shell: apparmor_status | grep "processes.*unconfined" | awk '{ print $1 }' 63 | changed_when: false 64 | failed_when: false 65 | register: ubtu20cis_rule_1_6_1_3_apparmor_unconfined 66 | 67 | - name: "1.6.1.3 | AUDIT | Ensure all AppArmor Profiles are in enforce or complain mode | Warning" 68 | ansible.builtin.debug: 69 | msg: "Warning!! AppArmor mode needs to be confirmed | Any unconfined processes may need to have a profile created" 70 | when: ubtu20cis_rule_1_6_1_3_apparmor_unconfined.stdout != '0' 71 | 72 | - name: "1.6.1.3 | AUDIT | Ensure all AppArmor Profiles are in enforce or complain mode | Warn Count" 73 | ansible.builtin.import_tasks: 74 | file: warning_facts.yml 75 | when: ubtu20cis_rule_1_6_1_3_apparmor_unconfined.stdout != '0' 76 | vars: 77 | warn_control_id: '1.6.1.3' 78 | when: 79 | - ubtu20cis_rule_1_6_1_3 80 | tags: 81 | - level1-server 82 | - level1-workstation 83 | - manual 84 | - patch 85 | - rule_1.6.1.3 86 | - apparmor 87 | 88 | # These tasks can be forced to run but some apps may not have profiles associated so this will need to be manual task 89 | - name: "1.6.1.4 | PATCH | Ensure all AppArmor Profiles are enforcing" 90 | block: 91 | - name: "1.6.1.4 | AUDIT | Ensure all AppArmor Profiles are enforcing | capture state" 92 | ansible.builtin.shell: apparmor_status | grep -E "processes.*complain" | awk '{ print $1 }' 93 | changed_when: false 94 | failed_when: false 95 | register: ubtu20cis_rule_1_6_1_4_apparmor_enforced 96 | 97 | - name: "1.6.1.4 | AUDIT | Ensure all AppArmor Profiles are enforcing | Warning" 98 | ansible.builtin.debug: 99 | msg: "Warning!! AppArmor mode is not in enforcing | Any unconfined processes may need to have a profile created" 100 | when: ubtu20cis_rule_1_6_1_4_apparmor_enforced.stdout != '0' 101 | 102 | - name: "1.6.1.4 | AUDIT | Ensure all AppArmor Profiles are enforcing | Warn Count" 103 | ansible.builtin.import_tasks: 104 | file: warning_facts.yml 105 | when: ubtu20cis_rule_1_6_1_4_apparmor_enforced.stdout != '0' 106 | 107 | vars: 108 | warn_control_id: '1.6.1.4' 109 | when: 110 | - ubtu20cis_rule_1_6_1_4 111 | tags: 112 | - level2-server 113 | - level2-workstation 114 | - automated 115 | - scored 116 | - patch 117 | - rule_1.6.1.4 118 | - apparmor 119 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.7.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "1.7.1 | PATCH | Ensure message of the day is configured properly" 3 | block: 4 | - name: "1.7.1 | PATCH | Ensure message of the day is configured properly | motd" 5 | ansible.builtin.template: 6 | src: etc/motd.j2 7 | dest: /etc/motd 8 | 9 | - name: "1.7.1 | PATCH | Ensure message of the day is configured properly | disable dynamic_motd" 10 | ansible.builtin.lineinfile: 11 | path: /etc/pam.d/sshd 12 | regexp: "{{ item.regexp }}" 13 | line: "{{ item.line }}" 14 | loop: 15 | - { regexp: 'session\s+optional\s+pam_motd.so\s+motd=/run/motd.dynamic', line: '# 1\' } 16 | - { regexp: 'session\s+optional\s+pam_motd.so noupdate', line: '# 1\' } 17 | - { regexp: '# Pam_motd.so diabled for CIS benchmark', line: '# Pam_motd.so diabled for CIS benchmark' } 18 | when: ubtu20cis_disable_dynamic_motd 19 | when: 20 | - ubtu20cis_rule_1_7_1 21 | tags: 22 | - level1-server 23 | - level1-workstation 24 | - automated 25 | - patch 26 | - rule_1.7.1 27 | - motd 28 | 29 | - name: "1.7.2 | PATCH | Ensure local login warning banner is configured properly" 30 | ansible.builtin.template: 31 | src: etc/issue.j2 32 | dest: /etc/issue 33 | when: 34 | - ubtu20cis_rule_1_7_2 35 | tags: 36 | - level1-server 37 | - level1-workstation 38 | - automated 39 | - patch 40 | - rule_1.7.2 41 | - banner 42 | 43 | - name: "1.7.3 | PATCH | Ensure remote login warning banner is configured properly" 44 | ansible.builtin.template: 45 | src: etc/issue.net.j2 46 | dest: /etc/issue.net 47 | when: 48 | - ubtu20cis_rule_1_7_3 49 | tags: 50 | - level1-server 51 | - level1-workstation 52 | - automated 53 | - patch 54 | - rule_1.7.3 55 | - banner 56 | 57 | - name: "1.7.4 | PATCH | Ensure permissions on /etc/motd are configured" 58 | ansible.builtin.file: 59 | path: /etc/motd 60 | owner: root 61 | group: root 62 | mode: 0644 63 | when: 64 | - ubtu20cis_rule_1_7_4 65 | tags: 66 | - level1-server 67 | - level1-workstation 68 | - automated 69 | - patch 70 | - rule_1.7.4 71 | - permissions 72 | - motd 73 | 74 | - name: "1.7.5 | PATCH | Ensure permissions on /etc/issue are configured" 75 | ansible.builtin.file: 76 | path: /etc/issue 77 | owner: root 78 | group: root 79 | mode: 0644 80 | when: 81 | - ubtu20cis_rule_1_7_5 82 | tags: 83 | - level1-server 84 | - level1-workstation 85 | - automated 86 | - patch 87 | - rule_1.7.5 88 | - permissions 89 | - banner 90 | 91 | - name: "1.7.6 | PATCH | Ensure permissions on /etc/issue.net are configured" 92 | ansible.builtin.file: 93 | path: /etc/issue.net 94 | owner: root 95 | group: root 96 | mode: 0644 97 | when: 98 | - ubtu20cis_rule_1_7_6 99 | tags: 100 | - level1-server 101 | - level1-workstation 102 | - automated 103 | - patch 104 | - rule_1.7.6 105 | - permissions 106 | - banner 107 | -------------------------------------------------------------------------------- /tasks/section_1/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "SECTION | 1.1.1 | Disable Unused Filesystems" 3 | ansible.builtin.import_tasks: 4 | file: cis_1.1.1.x.yml 5 | 6 | - name: "SECTION | 1.1.2 | Configure /tmp" 7 | ansible.builtin.import_tasks: 8 | file: cis_1.1.2.x.yml 9 | 10 | - name: "SECTION | 1.1.3 | Configure /var" 11 | ansible.builtin.import_tasks: 12 | file: cis_1.1.3.x.yml 13 | 14 | - name: "SECTION | 1.1.4 | Configure /var/tmp" 15 | ansible.builtin.import_tasks: 16 | file: cis_1.1.4.x.yml 17 | 18 | - name: "SECTION | 1.1.5 | Configure /var/log" 19 | ansible.builtin.import_tasks: 20 | file: cis_1.1.5.x.yml 21 | 22 | - name: "SECTION | 1.1.6 | Configure /var/log/audit" 23 | ansible.builtin.import_tasks: 24 | file: cis_1.1.6.x.yml 25 | 26 | - name: "SECTION | 1.1.7 | Configure /home" 27 | ansible.builtin.import_tasks: 28 | file: cis_1.1.7.x.yml 29 | 30 | - name: "SECTION | 1.1.8 | Configure /dev/shm" 31 | ansible.builtin.import_tasks: 32 | file: cis_1.1.8.x.yml 33 | 34 | - name: "SECTION | 1.1.9 | Configure autofs" 35 | ansible.builtin.import_tasks: 36 | file: cis_1.1.9.yml 37 | 38 | - name: "SECTION | 1.1.10 | Configure usb-storage" 39 | ansible.builtin.import_tasks: 40 | file: cis_1.1.10.yml 41 | 42 | - name: "SECTION | 1.2 | Filesystem Integrity Checking" 43 | ansible.builtin.import_tasks: 44 | file: cis_1.2.x.yml 45 | 46 | - name: "SECTION | 1.3. | gpg and repository configuration" 47 | ansible.builtin.import_tasks: 48 | file: cis_1.3.x.yml 49 | 50 | - name: "SECTION | 1.4 | Secure Boot Settings" 51 | ansible.builtin.import_tasks: 52 | file: cis_1.4.x.yml 53 | 54 | - name: "SECTION | 1.5 | Additional Process Hardening" 55 | ansible.builtin.import_tasks: 56 | file: cis_1.5.x.yml 57 | 58 | - name: "SECTION | 1.6 | Mandatory Access Control" 59 | ansible.builtin.import_tasks: 60 | file: cis_1.6.x.yml 61 | 62 | - name: "SECTION | 1.7 | Command Line Warning Banners" 63 | ansible.builtin.import_tasks: 64 | file: cis_1.7.x.yml 65 | 66 | - name: "SECTION | 1.8 | GNOME Display Manager" 67 | ansible.builtin.import_tasks: 68 | file: cis_1.8.x.yml 69 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.1.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.1.1.1 | PATCH | Ensure a single time synchronization daemon is in use" 4 | block: 5 | - name: "2.1.1.1 | PATCH | Ensure a single time synchronization daemon is in use | Pkg installed" 6 | ansible.builtin.package: 7 | name: "{{ ubtu20cis_time_sync_tool }}" 8 | state: present 9 | 10 | - name: "2.1.1.1 | PATCH | Ensure a single time synchronization daemon is in use | other pkgs removed" 11 | ansible.builtin.package: 12 | name: "{{ item }}" 13 | state: absent 14 | loop: 15 | - chrony 16 | - ntp 17 | when: item != ubtu20cis_time_sync_tool 18 | 19 | - name: "2.1.1.1 | PATCH | Ensure a single time synchronization daemon is in use | mask service" 20 | ansible.builtin.service: 21 | name: systemd-timesyncd.service 22 | state: stopped 23 | enabled: false 24 | masked: true 25 | when: 26 | - ubtu20cis_time_sync_tool != "systemd-timesyncd" 27 | - "'systemd-timesyncd' in ansible_facts.packages" 28 | when: 29 | - ubtu20cis_rule_2_1_1_1 30 | tags: 31 | - level1-server 32 | - level1-workstation 33 | - automated 34 | - patch 35 | - rule_2.1.1.1 36 | - chrony 37 | - ntp 38 | - systemd-timesyncd 39 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.1.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.1.2.1 | PATCH | Ensure chrony is configured with authorized timeserver" 4 | block: 5 | - name: "2.1.2.1 | PATCH | Ensure chrony is configured with authorized timeserver | ensure source dir exists" 6 | ansible.builtin.file: 7 | path: /etc/chrony/sources.d 8 | state: directory 9 | owner: root 10 | group: root 11 | mode: '0755' 12 | 13 | - name: "2.1.2.1 | PATCH | Ensure chrony is configured with authorized timeserver | sources" 14 | ansible.builtin.template: 15 | src: "{{ item }}.j2" 16 | dest: "/{{ item }}" 17 | mode: 0644 18 | owner: root 19 | group: root 20 | loop: 21 | - 'etc/chrony/sources.d/time.sources' 22 | notify: restart timeservice 23 | 24 | - name: "2.1.2.1 | PATCH | Ensure chrony is configured with authorized timeserver | remove current sources in .conf" 25 | ansible.builtin.replace: 26 | path: /etc/chrony/chrony.conf 27 | regexp: '^(server.*|pool.*)' 28 | replace: '#\1' 29 | notify: restart timeservice 30 | 31 | - name: "2.1.2.1 | PATCH | Ensure chrony is configured with authorized timeserver | load sources" 32 | ansible.builtin.lineinfile: 33 | path: /etc/chrony/chrony.conf 34 | regexp: '^include /etc/chrony/sources.d.*' 35 | line: include /etc/chrony/sources.d/time.sources 36 | notify: restart timeservice 37 | when: 38 | - ubtu20cis_rule_2_1_2_1 39 | tags: 40 | - level1-server 41 | - level1-workstation 42 | - patch 43 | - rule_2.1.2.1 44 | - chrony 45 | 46 | - name: "2.1.2.2 | PATCH | Ensure chrony is running as user _chrony" 47 | ansible.builtin.lineinfile: 48 | path: /etc/chrony/chrony.conf 49 | regexp: '^user _chrony' 50 | line: 'user _chrony' 51 | when: 52 | - ubtu20cis_rule_2_1_2_2 53 | tags: 54 | - level1-server 55 | - level1-workstation 56 | - patch 57 | - rule_2.1.2.2 58 | - chrony 59 | 60 | - name: "2.1.2.3 | PATCH | Ensure chrony is enabled and running" 61 | ansible.builtin.systemd: 62 | name: chrony 63 | state: started 64 | enabled: true 65 | when: 66 | - ubtu20cis_rule_2_1_2_3 67 | tags: 68 | - level1-server 69 | - level1-workstation 70 | - rule_2.1.2.3 71 | - chrony 72 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.1.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.1.3.1 | PATCH | Ensure systemd-timesyncd configured with authorized timeserver" 4 | block: 5 | - name: "2.1.3.1 | PATCH | Ensure systemd-timesyncd configured with authorized timeserver | conf directory" 6 | ansible.builtin.file: 7 | path: /etc/systemd/timesyncd.conf.d 8 | state: directory 9 | owner: root 10 | group: root 11 | mode: 0755 12 | 13 | - name: "2.1.3.1 | PATCH | Ensure systemd-timesyncd configured with authorized timeserver | sources" 14 | ansible.builtin.template: 15 | src: "{{ item }}.j2" 16 | dest: "/{{ item }}" 17 | mode: 0644 18 | owner: root 19 | group: root 20 | loop: 21 | - "etc/systemd/timesyncd.conf.d/50-timesyncd.conf" 22 | notify: restart timeservice 23 | when: 24 | - ubtu20cis_rule_2_1_3_1 25 | tags: 26 | - level1-server 27 | - level1-workstation 28 | - patch 29 | - rule_2.1.3.1 30 | - timesyncd 31 | 32 | - name: "2.1.3.2 | PATCH | Ensure systemd-timesyncd is enabled and running" 33 | ansible.builtin.systemd: 34 | name: systemd-timesyncd 35 | state: started 36 | enabled: true 37 | masked: false 38 | when: 39 | - ubtu20cis_rule_2_1_3_2 40 | tags: 41 | - level1-server 42 | - level1-workstation 43 | - rule_2.1.3.2 44 | - timesyncd 45 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.1.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.1.4.1 | PATCH | Ensure ntp access control is configured " 4 | ansible.builtin.lineinfile: 5 | path: /etc/ntp.conf 6 | regexp: '^(restrict) (|{{ item }}) .*$' 7 | line: 'restrict {{ item }} default kod nomodify notrap nopeer noquery' 8 | loop: 9 | - '-4' 10 | - '-6' 11 | notify: Restart timeservice 12 | when: 13 | - ubtu20cis_rule_2_1_4_1 14 | tags: 15 | - level1-server 16 | - level1-workstation 17 | - patch 18 | - rule_2.1.4.1 19 | - ntp 20 | 21 | - name: "2.1.4.2 | PATCH | Ensure ntp is configured with authorized timeserver" 22 | block: 23 | - name: "2.1.4.2 | PATCH | Ensure ntp is configured with authorized timeserver | pool" 24 | ansible.builtin.lineinfile: 25 | path: /etc/ntp.conf 26 | regexp: '^pool.*' 27 | line: 'pool {{ item.name }} {{ item.options }}' 28 | notify: Restart timeservice 29 | loop: "{{ ubtu20cis_time_pool }}" 30 | loop_control: 31 | label: "{{ item.name }}" 32 | 33 | - name: "2.1.4.2 | PATCH | Ensure ntp is configured with authorized timeserver | servers" 34 | ansible.builtin.lineinfile: 35 | path: /etc/ntp.conf 36 | insertafter: '^server' 37 | line: 'server {{ item.name }} {{ item.options }}' 38 | loop: "{{ ubtu20cis_time_servers }}" 39 | loop_control: 40 | label: "{{ item.name }}" 41 | notify: Restart timeservice 42 | when: 43 | - ubtu20cis_rule_2_1_4_2 44 | tags: 45 | - level1-server 46 | - level1-workstation 47 | - patch 48 | - rule_2.1.4.2 49 | - ntp 50 | 51 | - name: "2.1.4.3 | PATCH | Ensure ntp is running as user ntp" 52 | ansible.builtin.lineinfile: 53 | path: /etc/init.d/ntp 54 | regexp: '^RUNASUSER.*' 55 | line: 'RUNASUSER=ntp' 56 | notify: Restart timeservice 57 | when: 58 | - ubtu20cis_rule_2_1_4_3 59 | tags: 60 | - level1-server 61 | - level1-workstation 62 | - patch 63 | - rule_2.1.4.3 64 | - ntp 65 | 66 | - name: "2.1.4.4 | PATCH | Ensure ntp is enabled and running" 67 | ansible.builtin.systemd: 68 | name: ntp 69 | state: started 70 | enabled: true 71 | masked: false 72 | when: 73 | - ubtu20cis_rule_2_1_4_4 74 | tags: 75 | - level1-server 76 | - level1-workstation 77 | - rule_2.1.4.4 78 | - ntp 79 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.3.1 | PATCH | Ensure NIS Client is not installed" 4 | ansible.builtin.package: 5 | name: nis 6 | state: absent 7 | when: 8 | - ubtu20cis_rule_2_3_1 9 | - not ubtu20cis_nis_required 10 | tags: 11 | - level1-server 12 | - level1-workstation 13 | - rule_2.3.1 14 | - nis 15 | 16 | - name: "2.3.2 | PATCH | Ensure rsh client is not installed" 17 | ansible.builtin.package: 18 | name: rsh-client 19 | state: absent 20 | when: 21 | - ubtu20cis_rule_2_3_2 22 | - not ubtu20cis_rsh_required 23 | tags: 24 | - level1-server 25 | - level1-workstation 26 | - automated 27 | - patch 28 | - rule_2.3.2 29 | - rsh 30 | 31 | - name: "2.3.3 | PATCH | Ensure talk client is not installed" 32 | ansible.builtin.package: 33 | name: talk 34 | state: absent 35 | when: 36 | - ubtu20cis_rule_2_3_3 37 | - not ubtu20cis_talk_required 38 | tags: 39 | - level1-server 40 | - level1-workstation 41 | - automated 42 | - patch 43 | - rule_2.3.3 44 | - talk 45 | 46 | - name: "2.3.4 | PATCH | Ensure telnet client is not installed" 47 | ansible.builtin.package: 48 | name: telnet 49 | state: absent 50 | when: 51 | - ubtu20cis_rule_2_3_4 52 | - not ubtu20cis_telnet_required 53 | tags: 54 | - level1-server 55 | - level1-workstation 56 | - automated 57 | - patch 58 | - rule_2.3.4 59 | - telnet 60 | 61 | - name: "2.3.5 | PATCH | Ensure LDAP client is not installed" 62 | ansible.builtin.package: 63 | name: ldap-utils 64 | state: absent 65 | when: 66 | - ubtu20cis_rule_2_3_5 67 | - not ubtu20cis_ldap_clients_required 68 | tags: 69 | - level1-server 70 | - level1-workstation 71 | - automated 72 | - patch 73 | - rule_2.3.5 74 | - ldap 75 | 76 | - name: "2.3.6 | PATCH | Ensure RPC is not installed" 77 | ansible.builtin.package: 78 | name: rpcbind 79 | state: absent 80 | when: 81 | - ubtu20cis_rule_2_3_6 82 | - not ubtu20cis_rpc_required 83 | tags: 84 | - level1-server 85 | - level1-workstation 86 | - automated 87 | - patch 88 | - rule_2.3.6 89 | - rpbc 90 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.4.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.4 | AUDIT | Ensure nonessential services are removed or masked" 4 | block: 5 | - name: "2.4 | AUDIT | Ensure nonessential services are removed or masked | Check for services" 6 | ansible.builtin.shell: lsof -i -P -n | grep -v "(ESTABLISHED)" 7 | changed_when: false 8 | failed_when: false 9 | check_mode: false 10 | register: ubtu20cis_2_3_services 11 | 12 | - name: "2.4 | AUDIT | Ensure nonessential services are removed or masked | Message out running services" 13 | ansible.builtin.debug: 14 | msg: 15 | - "Warning!! Below are the running services. Please review and remove as well as mask un-needed services" 16 | - "{{ ubtu20cis_2_3_services.stdout_lines }}" 17 | when: ubtu20cis_2_3_services.stdout | length > 0 18 | 19 | - name: "2.4 | AUDIT | Ensure nonessential services are removed or masked | Set warning count" 20 | ansible.builtin.import_tasks: 21 | file: warning_facts.yml 22 | when: ubtu20cis_2_3_services.stdout | length > 0 23 | vars: 24 | warn_control_id: '2.4' 25 | when: 26 | - ubtu20cis_rule_2_4 27 | tags: 28 | - level1-server 29 | - level1-workstation 30 | - manual 31 | - audit 32 | - rule_2.4 33 | - services 34 | -------------------------------------------------------------------------------- /tasks/section_2/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "SECTION | 2.1.1 | Configure Time Synchronization" 3 | ansible.builtin.import_tasks: 4 | file: cis_2.1.1.x.yml 5 | 6 | - name: "SECTION | 2.1.2 | Configure chrony" 7 | ansible.builtin.import_tasks: 8 | file: cis_2.1.2.x.yml 9 | when: ubtu20cis_time_sync_tool == "chrony" 10 | 11 | - name: "SECTION | 2.1.3 | Configure systemd-timesyncd" 12 | ansible.builtin.import_tasks: 13 | file: cis_2.1.3.x.yml 14 | when: ubtu20cis_time_sync_tool == "systemd-timesyncd" 15 | 16 | - name: "SECTION | 2.1.4 | Configure NTP" 17 | ansible.builtin.import_tasks: 18 | file: cis_2.1.4.x.yml 19 | when: ubtu20cis_time_sync_tool == "ntp" 20 | 21 | - name: "SECTION | 2.2 | Special Purpose Services" 22 | ansible.builtin.import_tasks: 23 | file: cis_2.2.x.yml 24 | 25 | - name: "SECTION | 2.3 | Service Clients" 26 | ansible.builtin.import_tasks: 27 | file: cis_2.3.x.yml 28 | 29 | - name: "SECTION | 2.4 | Ensure nonessential services are removed or masked" 30 | ansible.builtin.import_tasks: 31 | file: cis_2.4.yml 32 | -------------------------------------------------------------------------------- /tasks/section_3/cis_3.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "3.1.1 | PATCH | Disable IPv6" 4 | block: 5 | - name: "3.1.1 | AUDIT | Disable IPv6 | Get current GRUB_CMDLINE_LINUX settings" 6 | ansible.builtin.shell: grep "GRUB_CMDLINE_LINUX=" /etc/default/grub | cut -f2 -d'"' 7 | changed_when: false 8 | failed_when: false 9 | check_mode: false 10 | register: ubtu20cis_3_1_1_grub_cmdline_linux_settings 11 | 12 | - name: "3.1.1 | PATCH | Disable IPv6 | Add ipv6.disable if does not exist" 13 | ansible.builtin.lineinfile: 14 | path: /etc/default/grub 15 | regexp: '^GRUB_CMDLINE_LINUX' 16 | line: 'GRUB_CMDLINE_LINUX="{{ ubtu20cis_3_1_1_grub_cmdline_linux_settings.stdout }} ipv6.disable=1"' 17 | when: "'ipv6.disable' not in ubtu20cis_3_1_1_grub_cmdline_linux_settings.stdout" 18 | notify: Grub update 19 | 20 | - name: "3.1.1 | PATCH | Disable IPv6 | Set ipv6.disable to 1 if exists" 21 | ansible.builtin.replace: 22 | path: /etc/default/grub 23 | regexp: 'ipv6\.disable=.' 24 | replace: 'ipv6.disable=1' 25 | when: "'ipv6.disable' in ubtu20cis_3_1_1_grub_cmdline_linux_settings.stdout" 26 | notify: Grub update 27 | 28 | - name: "3.1.1 | PATCH | Disable IPv6 | Remove net.ipv6.conf.all.disable_ipv6" 29 | ansible.builtin.lineinfile: 30 | path: /etc/sysctl.conf 31 | regexp: '^net.ipv6.conf.all.disable_ipv6.*' 32 | state: absent 33 | when: 34 | - ubtu20cis_rule_3_1_1 35 | - not ubtu20cis_ipv6_required 36 | tags: 37 | - level2-server 38 | - level2-workstation 39 | - manual 40 | - patch 41 | - rule_3.1.1 42 | - ipv6 43 | 44 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled" 45 | block: 46 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled | Check for wifi status " 47 | ansible.builtin.shell: nmcli radio wifi 48 | changed_when: false 49 | failed_when: false 50 | check_mode: false 51 | register: ubtu20cis_3_1_2_wifi_status 52 | when: 53 | - ubtu20cis_install_network_manager 54 | 55 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled | Disable wireless if enabled nmcli" 56 | ansible.builtin.shell: nmcli radio all off 57 | changed_when: ubtu20cis_3_1_2_nmcli_radio_off.rc == 0 58 | register: ubtu20cis_3_1_2_nmcli_radio_off 59 | when: 60 | - ubtu20cis_install_network_manager 61 | - ubtu20cis_3_1_2_wifi_status.stdout == 'enabled' 62 | 63 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled | disable driver" 64 | ansible.builtin.lineinfile: 65 | path: /etc/modprobe.d/disable_wireless.conf 66 | regexp: 'install\s*{{ item }}\s*/bin/true' 67 | line: "install {{ item.line }} /bin/true" 68 | create: true 69 | loop: 70 | - "{{ wireless_interfaces.stdout_lines }}" 71 | 72 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are not loadable | blacklist" 73 | ansible.builtin.lineinfile: 74 | path: /etc/modprobe.d/blacklist.conf 75 | regexp: 'blacklist\s*{{ item }}\s*' 76 | line: "blacklist {{ item.line }}" 77 | create: true 78 | loop: 79 | - "{{ wireless_interfaces.stdout_lines }}" 80 | 81 | when: 82 | - ubtu20cis_rule_3_1_2 83 | - wireless_interfaces.stdout | length > 0 84 | tags: 85 | - level1-server 86 | - level2-workstation 87 | - automated 88 | - patch 89 | - rule_3.1.2 90 | - wireless 91 | 92 | - name: "3.1.3 | PATCH | Ensure bluetooth is disabled" 93 | ansible.builtin.systemd: 94 | name: bluetooth.service 95 | state: stopped 96 | enabled: false 97 | masked: true 98 | when: 99 | - ubtu20cis_rule_3_1_3 100 | - "'bluetooth' in ansible_facts.packages" 101 | tags: 102 | - level1-server 103 | - automated 104 | - patch 105 | - rule_3.1.3 106 | - bluetooth 107 | 108 | - name: "3.1.4 | PATCH | Ensure DCCP is disabled" 109 | block: 110 | - name: "3.1.4 | PATCH | Ensure DCCP is disabled | modprobe" 111 | ansible.builtin.lineinfile: 112 | path: /etc/modprobe.d/dccp.conf 113 | regexp: '^(#)?install dccp(\\s|$)' 114 | line: 'install dccp /bin/true' 115 | create: true 116 | 117 | - name: "3.1.4 | PATCH | Ensure DCCP is disabled | blacklist" 118 | ansible.builtin.lineinfile: 119 | path: /etc/modprobe.d/blacklist.conf 120 | regexp: '^(#)?blacklist dccp(\\s|$)' 121 | line: 'blacklist dccp' 122 | create: true 123 | when: 124 | - ubtu20cis_rule_3_1_4 125 | tags: 126 | - level2-server 127 | - level2-workstation 128 | - automated 129 | - patch 130 | - rule_3.1.4 131 | - dccp 132 | 133 | - name: "3.1.5 | PATCH | Ensure SCTP is disabled" 134 | block: 135 | - name: "3.1.5 | PATCH | Ensure SCTP is disabled | modprobe" 136 | ansible.builtin.lineinfile: 137 | path: /etc/modprobe.d/sctp.conf 138 | regexp: "^(#)?install sctp(\\s|$)" 139 | line: 'install sctp /bin/true' 140 | create: true 141 | 142 | - name: "3.1.5 | PATCH | Ensure SCTP is disabled | blacklist" 143 | ansible.builtin.lineinfile: 144 | path: /etc/modprobe.d/blacklist.conf 145 | regexp: "^(#)?blacklist sctp(\\s|$)" 146 | line: 'blacklist sctp' 147 | create: true 148 | 149 | when: 150 | - ubtu20cis_rule_3_1_5 151 | tags: 152 | - level2-server 153 | - level2-workstation 154 | - automated 155 | - patch 156 | - rule_3.1.5 157 | - sctp 158 | 159 | - name: "3.1.6 | PATCH | Ensure RDS is disabled" 160 | block: 161 | - name: "3.1.6 | PATCH | Ensure RDS is disabled | modprobe" 162 | ansible.builtin.lineinfile: 163 | path: /etc/modprobe.d/rds.conf 164 | regexp: '^(#)?install rds(\\s|$)' 165 | line: 'install rds /bin/true' 166 | create: true 167 | 168 | - name: "3.1.6 | PATCH | Ensure RDS is disabled | blacklist" 169 | ansible.builtin.lineinfile: 170 | path: /etc/modprobe.d/blacklist.conf 171 | regexp: "^(#)?blacklist rds(\\s|$)" 172 | line: 'blacklist rds' 173 | create: true 174 | when: 175 | - ubtu20cis_rule_3_1_6 176 | tags: 177 | - level2-server 178 | - level2-workstation 179 | - automated 180 | - patch 181 | - rule_3.1.6 182 | - rds 183 | 184 | - name: "3.1.7 | PATCH | Ensure TIPC is disabled" 185 | block: 186 | - name: "3.1.7 | PATCH | Ensure TIPC is disabled | modprobe" 187 | ansible.builtin.lineinfile: 188 | path: /etc/modprobe.d/tipc.conf 189 | regexp: '^(#)?install tipc(\\s|$)' 190 | line: install tipc /bin/true 191 | create: true 192 | 193 | - name: "3.1.7 | PATCH | Ensure TIPC is disabled | blacklist" 194 | ansible.builtin.lineinfile: 195 | path: /etc/modprobe.d/blacklist.conf 196 | regexp: "^(#)?blacklist tipc(\\s|$)" 197 | line: 'blacklist tipc' 198 | create: true 199 | when: 200 | - ubtu20cis_rule_3_1_7 201 | tags: 202 | - level2-server 203 | - level2-workstation 204 | - automated 205 | - patch 206 | - rule_3.1.7 207 | - tipc 208 | -------------------------------------------------------------------------------- /tasks/section_3/cis_3.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "3.2.1 | PATCH | Ensure packet redirect sending is disabled" 3 | ansible.posix.sysctl: 4 | name: "{{ item }}" 5 | value: '0' 6 | sysctl_set: true 7 | state: present 8 | reload: true 9 | ignoreerrors: true 10 | with_items: 11 | - net.ipv4.conf.all.send_redirects 12 | - net.ipv4.conf.default.send_redirects 13 | notify: sysctl flush ipv4 route table 14 | when: 15 | - ubtu20cis_rule_3_2_1 16 | - not ubtu20cis_is_router 17 | tags: 18 | - level1-server 19 | - level1-workstation 20 | - automated 21 | - patch 22 | - rule_3.2.1 23 | - packet_redirect 24 | - sysctl 25 | 26 | - name: "3.2.2 | PATCH | Ensure IP forwarding is disabled" 27 | block: 28 | - name: "3.2.2 | PATCH | Ensure IP forwarding is disabled | IPv4 settings" 29 | ansible.posix.sysctl: 30 | name: net.ipv4.ip_forward 31 | value: '0' 32 | sysctl_set: true 33 | state: present 34 | reload: true 35 | ignoreerrors: true 36 | notify: 37 | - sysctl flush ipv4 route table 38 | 39 | - name: "3.2.2 | PATCH | Ensure IP forwarding is disabled | IPv6 settings" 40 | ansible.posix.sysctl: 41 | name: net.ipv6.conf.all.forwarding 42 | value: '0' 43 | sysctl_set: true 44 | state: present 45 | reload: true 46 | ignoreerrors: true 47 | notify: 48 | - sysctl flush ipv6 route table 49 | when: ubtu20cis_ipv6_required 50 | when: 51 | - ubtu20cis_rule_3_2_2 52 | - not ubtu20cis_is_router 53 | tags: 54 | - level1-server 55 | - level1-workstation 56 | - automated 57 | - patch 58 | - rule_3.2.2 59 | - ip_forwarding 60 | - sysctl 61 | -------------------------------------------------------------------------------- /tasks/section_3/cis_3.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "3.3.1 | PATCH | Ensure source routed packets are not accepted" 3 | block: 4 | - name: "3.3.1 | PATCH | Ensure source routed packets are not accepted | IPv4 settings" 5 | ansible.posix.sysctl: 6 | name: "{{ item }}" 7 | value: '0' 8 | sysctl_set: true 9 | state: present 10 | reload: true 11 | ignoreerrors: true 12 | with_items: 13 | - net.ipv4.conf.all.accept_source_route 14 | - net.ipv4.conf.default.accept_source_route 15 | notify: sysctl flush ipv4 route table 16 | 17 | - name: "3.3.1 | PATCH | Ensure source routed packets are not accepted | IPv6 settings" 18 | ansible.posix.sysctl: 19 | name: "{{ item }}" 20 | value: '0' 21 | sysctl_set: true 22 | state: present 23 | reload: true 24 | ignoreerrors: true 25 | with_items: 26 | - net.ipv6.conf.all.accept_source_route 27 | - net.ipv6.conf.default.accept_source_route 28 | notify: sysctl flush ipv6 route table 29 | when: ubtu20cis_ipv6_required 30 | when: 31 | - ubtu20cis_rule_3_3_1 32 | - not ubtu20cis_is_router 33 | tags: 34 | - level1-server 35 | - level1-workstation 36 | - automated 37 | - patch 38 | - rule_3.3.1 39 | - routed_packets 40 | - sysctl 41 | 42 | - name: "3.3.2 | PATCH | Ensure ICMP redirects are not accepted" 43 | block: 44 | - name: "3.3.2 | PATCH | Ensure ICMP redirects are not accepted | IPv4 settings" 45 | ansible.posix.sysctl: 46 | name: "{{ item }}" 47 | value: '0' 48 | sysctl_set: true 49 | state: present 50 | reload: true 51 | ignoreerrors: true 52 | with_items: 53 | - net.ipv4.conf.all.accept_redirects 54 | - net.ipv4.conf.default.accept_redirects 55 | notify: sysctl flush ipv4 route table 56 | 57 | - name: "3.3.2 | PATCH | Ensure ICMP redirects are not accepted | IPv6 settings" 58 | ansible.posix.sysctl: 59 | name: "{{ item }}" 60 | value: '0' 61 | sysctl_set: true 62 | state: present 63 | reload: true 64 | ignoreerrors: true 65 | with_items: 66 | - net.ipv6.conf.all.accept_redirects 67 | - net.ipv6.conf.default.accept_redirects 68 | notify: sysctl flush ipv6 route table 69 | when: ubtu20cis_ipv6_required 70 | when: 71 | - ubtu20cis_rule_3_3_2 72 | tags: 73 | - level1-server 74 | - level1-workstation 75 | - automated 76 | - patch 77 | - rule_3.3.2 78 | - icmp 79 | - sysctl 80 | 81 | - name: "3.3.3 | PATCH | Ensure secure ICMP redirects are not accepted" 82 | ansible.posix.sysctl: 83 | name: "{{ item }}" 84 | value: '0' 85 | sysctl_set: true 86 | state: present 87 | reload: true 88 | ignoreerrors: true 89 | with_items: 90 | - net.ipv4.conf.all.secure_redirects 91 | - net.ipv4.conf.default.secure_redirects 92 | notify: sysctl flush ipv4 route table 93 | when: 94 | - ubtu20cis_rule_3_3_3 95 | tags: 96 | - level1-server 97 | - level1-workstation 98 | - automated 99 | - patch 100 | - rule_3.3.3 101 | - icmp 102 | - sysctl 103 | 104 | - name: "3.3.4 | PATCH | Ensure suspicious packets are logged" 105 | ansible.posix.sysctl: 106 | name: "{{ item }}" 107 | value: '1' 108 | sysctl_set: true 109 | state: present 110 | reload: true 111 | ignoreerrors: true 112 | with_items: 113 | - net.ipv4.conf.all.log_martians 114 | - net.ipv4.conf.default.log_martians 115 | notify: sysctl flush ipv4 route table 116 | when: 117 | - ubtu20cis_rule_3_3_4 118 | tags: 119 | - level1-server 120 | - level1-workstation 121 | - automated 122 | - patch 123 | - rule_3.3.4 124 | - suspicious_packets 125 | - sysctl 126 | 127 | - name: "3.3.5 | PATCH | Ensure broadcast ICMP requests are ignored" 128 | ansible.posix.sysctl: 129 | name: net.ipv4.icmp_echo_ignore_broadcasts 130 | value: '1' 131 | sysctl_set: true 132 | state: present 133 | reload: true 134 | ignoreerrors: true 135 | notify: sysctl flush ipv4 route table 136 | when: 137 | - ubtu20cis_rule_3_3_5 138 | tags: 139 | - level1-server 140 | - level1-workstation 141 | - automated 142 | - patch 143 | - rule_3.3.5 144 | - icmp 145 | - sysctl 146 | 147 | - name: "3.3.6 | PATCH | Ensure bogus ICMP responses are ignored" 148 | ansible.posix.sysctl: 149 | name: net.ipv4.icmp_ignore_bogus_error_responses 150 | value: '1' 151 | sysctl_set: true 152 | state: present 153 | reload: true 154 | ignoreerrors: true 155 | notify: sysctl flush ipv4 route table 156 | when: 157 | - ubtu20cis_rule_3_3_6 158 | tags: 159 | - level1-server 160 | - level1-workstation 161 | - automated 162 | - patch 163 | - rule_3.3.6 164 | - icmp 165 | - sysctl 166 | 167 | - name: "3.3.7 | PATCH | Ensure Reverse Path Filtering is enabled" 168 | ansible.posix.sysctl: 169 | name: "{{ item }}" 170 | value: '1' 171 | sysctl_set: true 172 | state: present 173 | reload: true 174 | ignoreerrors: true 175 | with_items: 176 | - net.ipv4.conf.all.rp_filter 177 | - net.ipv4.conf.default.rp_filter 178 | notify: sysctl flush ipv4 route table 179 | when: 180 | - ubtu20cis_rule_3_3_7 181 | tags: 182 | - level1-server 183 | - level1-workstation 184 | - automated 185 | - patch 186 | - rule_3.3.7 187 | - reverse_path_filtering 188 | - sysctl 189 | 190 | - name: "3.3.8 | PATCH | Ensure TCP SYN Cookies is enabled" 191 | ansible.posix.sysctl: 192 | name: net.ipv4.tcp_syncookies 193 | value: '1' 194 | sysctl_set: true 195 | state: present 196 | reload: true 197 | ignoreerrors: true 198 | notify: sysctl flush ipv4 route table 199 | when: 200 | - ubtu20cis_rule_3_3_8 201 | tags: 202 | - level1-server 203 | - level1-workstation 204 | - automated 205 | - patch 206 | - rule_3.3.8 207 | - tcp_syn_cookies 208 | - sysctl 209 | 210 | - name: "3.3.9 | PATCH | Ensure IPv6 router advertisements are not accepted" 211 | ansible.posix.sysctl: 212 | name: "{{ item }}" 213 | value: '0' 214 | sysctl_set: true 215 | state: present 216 | reload: true 217 | ignoreerrors: true 218 | with_items: 219 | - net.ipv6.conf.all.accept_ra 220 | - net.ipv6.conf.default.accept_ra 221 | notify: sysctl flush ipv6 route table 222 | when: 223 | - ubtu20cis_rule_3_3_9 224 | - ubtu20cis_ipv6_required 225 | tags: 226 | - level1-server 227 | - level1-workstation 228 | - automated 229 | - patch 230 | - rule_3.3.9 231 | - ipv6 232 | - router_advertisements 233 | - sysctl 234 | -------------------------------------------------------------------------------- /tasks/section_3/cis_3.4.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "3.4.1.1 | PATCH | Ensure ufw is installed" 3 | ansible.builtin.package: 4 | name: ufw 5 | state: present 6 | when: 7 | - ubtu20cis_rule_3_4_1_1 8 | - ubtu20cis_firewall_package == "ufw" 9 | tags: 10 | - level1-server 11 | - level1-workstation 12 | - automated 13 | - patch 14 | - rule_3.4.1.1 15 | - apt 16 | - ufw 17 | 18 | - name: "3.4.1.2 | PATCH | Ensure iptables-persistent is not installed with ufw" 19 | ansible.builtin.package: 20 | name: iptables-persistent 21 | state: absent 22 | when: 23 | - ubtu20cis_rule_3_4_1_2 24 | - ubtu20cis_firewall_package == "ufw" 25 | tags: 26 | - level1-server 27 | - level1-workstation 28 | - automated 29 | - patch 30 | - rule_3.4.1.2 31 | - ufw 32 | 33 | # Adding the allow OpenSSH rule while enabling ufw to allow ansible to run after enabling 34 | - name: "3.4.1.3 | PATCH | Ensure ufw service is enabled" 35 | community.general.ufw: 36 | rule: allow 37 | name: OpenSSH 38 | state: enabled 39 | when: 40 | - ubtu20cis_rule_3_4_1_3 41 | - ubtu20cis_firewall_package == "ufw" 42 | tags: 43 | - level1-server 44 | - level1-workstation 45 | - automated 46 | - patch 47 | - rule_3.4.1.3 48 | - ufw 49 | 50 | - name: "3.4.1.4 | PATCH | Ensure loopback traffic is configured" 51 | block: 52 | - name: "3.4.1.4 | PATCH | Ensure loopback traffic is configured | Set allow in ufw rules" 53 | community.general.ufw: 54 | rule: allow 55 | direction: in 56 | interface: lo 57 | notify: reload ufw 58 | 59 | - name: "3.4.1.4 | PATCH | Ensure loopback traffic is configured | Set allow out ufw rules" 60 | community.general.ufw: 61 | rule: allow 62 | direction: out 63 | interface: lo 64 | notify: reload ufw 65 | 66 | - name: "3.4.1.4 | PATCH | Ensure loopback traffic is configured | Set deny ufw rules IPv4" 67 | community.general.ufw: 68 | rule: deny 69 | direction: in 70 | from_ip: 127.0.0.0/8 71 | notify: reload ufw 72 | 73 | - name: "3.4.1.4 | PATCH | Ensure loopback traffic is configured | Set deny ufw rules IPv6" 74 | community.general.ufw: 75 | rule: deny 76 | direction: in 77 | from_ip: "::1" 78 | notify: reload ufw 79 | when: ubtu20cis_ipv6_required 80 | when: 81 | - ubtu20cis_rule_3_4_1_4 82 | - ubtu20cis_firewall_package == "ufw" 83 | tags: 84 | - level1-server 85 | - level1-workstation 86 | - automated 87 | - patch 88 | - rule_3.4.1.4 89 | - ufw 90 | 91 | - name: "3.4.1.5 | PATCH | Ensure ufw outbound connections are configured" 92 | block: 93 | - name: "3.4.1.5 | PATCH | Ensure ufw outbound connections are configured | Custom ports" 94 | community.general.ufw: 95 | rule: allow 96 | direction: out 97 | to_port: '{{ item }}' 98 | with_items: 99 | - "{{ ubtu20cis_ufw_allow_out_ports }}" 100 | notify: reload ufw 101 | when: ubtu20cis_ufw_allow_out_ports != "all" 102 | 103 | - name: "3.4.1.5 | PATCH | Ensure ufw outbound connections are configured | Allow all" 104 | community.general.ufw: 105 | rule: allow 106 | direction: out 107 | notify: reload ufw 108 | when: "'all' in ubtu20cis_ufw_allow_out_ports" 109 | when: 110 | - ubtu20cis_rule_3_4_1_5 111 | - ubtu20cis_firewall_package == "ufw" 112 | tags: 113 | - level1-server 114 | - level1-workstation 115 | - manual 116 | - patch 117 | - rule_3.4.1.5 118 | - ufw 119 | 120 | - name: "3.4.1.6 | AUDIT | Ensure ufw firewall rules exist for all open ports" 121 | block: 122 | - name: "3.4.1.6 | AUDIT | Ensure ufw firewall rules exist for all open ports | Get list of open ports" 123 | ansible.builtin.shell: ss -4tuln 124 | changed_when: false 125 | failed_when: false 126 | check_mode: false 127 | register: ubtu20cis_3_4_1_6_open_listen_ports 128 | 129 | - name: "3.4.1.6 | AUDIT | Ensure ufw firewall rules exist for all open ports | Get list of firewall rules" 130 | ansible.builtin.shell: ufw status 131 | changed_when: false 132 | failed_when: false 133 | check_mode: false 134 | register: ubtu20cis_3_4_1_6_firewall_rules 135 | 136 | - name: "3.4.1.6 | AUDIT | Ensure ufw firewall rules exist for all open ports | Message out settings" 137 | ansible.builtin.debug: 138 | msg: 139 | - "Warning!! Below are the listening ports and firewall rules" 140 | - "Please create firewall rule for any open ports if not already done" 141 | - "*****---Open Listen Ports---*****" 142 | - "{{ ubtu20cis_3_4_1_6_open_listen_ports.stdout_lines }}" 143 | - "*****---Firewall Rules---*****" 144 | - "{{ ubtu20cis_3_4_1_6_firewall_rules.stdout_lines }}" 145 | 146 | - name: "3.4.1.6 | AUDIT | Ensure ufw firewall rules exist for all open ports | Warn Count" 147 | ansible.builtin.import_tasks: 148 | file: warning_facts.yml 149 | vars: 150 | warn_control_id: '3.4.1.6' 151 | 152 | when: 153 | - ubtu20cis_rule_3_4_1_6 154 | - ubtu20cis_firewall_package == "ufw" 155 | tags: 156 | - level1-server 157 | - level1-workstation 158 | - manual 159 | - audit 160 | - rule_3.4.1.6 161 | - ufw 162 | 163 | - name: "OPTIONAL | UFW incoming ports" 164 | community.general.ufw: 165 | rule: allow 166 | direction: in 167 | from_ip: '{{ item.from }}' 168 | to_port: '{{ item.port }}' 169 | proto: '{{ item.proto }}' 170 | loop: "{{ ubtu20cis_ufw_allow_in }}" 171 | loop_control: 172 | label: "{{ item.port }}" 173 | notify: reload ufw 174 | when: ufw_add_incoming 175 | tags: 176 | - ufw 177 | - ufw_in_ports 178 | 179 | - name: "3.4.1.7 | PATCH | Ensure ufw default deny firewall policy" 180 | community.general.ufw: 181 | default: deny 182 | direction: "{{ item }}" 183 | notify: reload ufw 184 | with_items: 185 | - incoming 186 | - outgoing 187 | - routed 188 | when: 189 | - ubtu20cis_rule_3_4_1_7 190 | - ubtu20cis_firewall_package == "ufw" 191 | tags: 192 | - level1-server 193 | - level1-workstation 194 | - automated 195 | - patch 196 | - rule_3.4.1.7 197 | - ufw 198 | -------------------------------------------------------------------------------- /tasks/section_3/cis_3.4.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # --------------- 4 | # --------------- 5 | # NFTables is unsupported with this role. However we have the actions commented out as a guide 6 | # --------------- 7 | # --------------- 8 | 9 | - name: "3.4.2.1 | AUDIT | Ensure nftables is installed" 10 | ansible.builtin.package: 11 | name: nftables 12 | state: present 13 | when: 14 | - ubtu20cis_rule_3_4_2_1 15 | - ubtu20cis_firewall_package == "nftables" 16 | tags: 17 | - level1-server 18 | - level1-workstation 19 | - automated 20 | - audit 21 | - rule_3.4.2.1 22 | - nftables 23 | 24 | - name: "3.4.2.2 | AUDIT | Ensure ufw is uninstalled or disabled with nftables" 25 | ansible.builtin.package: 26 | name: ufw 27 | state: absent 28 | when: 29 | - ubtu20cis_rule_3_4_2_2 30 | - ubtu20cis_firewall_package == "nftables" 31 | tags: 32 | - level1-server 33 | - level1-workstation 34 | - automated 35 | - audit 36 | - rule_3.4.2.2 37 | - nftables 38 | 39 | - name: "3.4.2.3 | AUDIT | Ensure iptables are flushed with nftables" 40 | block: 41 | - name: "3.4.2.3 | AUDIT | Ensure iptables are flushed with nftables" 42 | ansible.builtin.debug: 43 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 44 | 45 | - name: "3.4.2.3 | AUDIT | Ensure iptables are flushed with nftables | Warn Count" 46 | ansible.builtin.import_tasks: 47 | file: warning_facts.yml 48 | vars: 49 | warn_control_id: '3.4.2.3' 50 | when: 51 | - ubtu20cis_rule_3_4_2_3 52 | - ubtu20cis_firewall_package == "nftables" 53 | tags: 54 | - level1-server 55 | - level1-workstation 56 | - manual 57 | - audit 58 | - rule_3.4.2.3 59 | - nftables 60 | 61 | - name: "3.4.2.4 | AUDIT | Ensure a nftables table exists" 62 | block: 63 | - name: "3.4.2.4 | AUDIT | Ensure a nftables table exists" 64 | ansible.builtin.debug: 65 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 66 | 67 | - name: "3.4.2.4 | AUDIT | Ensure a nftables table exists | Warn Count" 68 | ansible.builtin.import_tasks: 69 | file: warning_facts.yml 70 | vars: 71 | warn_control_id: '3.4.2.4' 72 | when: 73 | - ubtu20cis_rule_3_4_2_4 74 | - ubtu20cis_firewall_package == "nftables" 75 | tags: 76 | - level1-server 77 | - level1-workstation 78 | - automated 79 | - patch 80 | - rule_3.4.2.4 81 | - nftables 82 | 83 | - name: "3.4.2.5 | AUDIT | Ensure nftables base chains exist" 84 | block: 85 | - name: "3.4.2.5 | AUDIT | Ensure nftables base chains exist" 86 | ansible.builtin.debug: 87 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 88 | 89 | - name: "3.4.2.5 | AUDIT | Ensure nftables base chains exist | Warn Count" 90 | ansible.builtin.import_tasks: 91 | file: warning_facts.yml 92 | vars: 93 | warn_control_id: '3.4.2.5' 94 | when: 95 | - ubtu20cis_rule_3_4_2_5 96 | - ubtu20cis_firewall_package == "nftables" 97 | tags: 98 | - level1-server 99 | - level1-workstation 100 | - automated 101 | - audit 102 | - rule_3.4.2.5 103 | - nftables 104 | 105 | - name: "3.4.2.6 | AUDIT | Ensure nftables loopback traffic is configured" 106 | block: 107 | - name: "3.4.2.6 | AUDIT | Ensure nftables loopback traffic is configured" 108 | ansible.builtin.debug: 109 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 110 | 111 | - name: "3.4.2.6 | AUDIT | Ensure nftables loopback traffic is configured | Warn Count" 112 | ansible.builtin.import_tasks: 113 | file: warning_facts.yml 114 | vars: 115 | warn_control_id: '3.4.2.6' 116 | when: 117 | - ubtu20cis_rule_3_4_2_6 118 | - ubtu20cis_firewall_package == "nftables" 119 | tags: 120 | - level1-server 121 | - level1-workstation 122 | - automated 123 | - audit 124 | - rule_3.4.2.6 125 | - nftables 126 | 127 | - name: "3.4.2.7 | AUDIT | Ensure nftables outbound and established connections are configured" 128 | block: 129 | - name: "3.4.2.7 | AUDIT | Ensure nftables outbound and established connections are configured" 130 | ansible.builtin.debug: 131 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 132 | 133 | - name: "3.4.2.7 | AUDIT | Ensure nftables outbound and established connections are configured | Warn Count" 134 | ansible.builtin.import_tasks: 135 | file: warning_facts.yml 136 | vars: 137 | warn_control_id: '3.4.2.7' 138 | when: 139 | - ubtu20cis_rule_3_4_2_7 140 | - ubtu20cis_firewall_package == "nftables" 141 | tags: 142 | - level1-server 143 | - level1-workstation 144 | - manual 145 | - audit 146 | - rule_3.4.2.7 147 | - nftables 148 | 149 | - name: "3.4.2.8 | AUDIT | Ensure nftables default deny firewall policy" 150 | block: 151 | - name: "3.4.2.8 | AUDIT | Ensure nftables default deny firewall policy" 152 | ansible.builtin.debug: 153 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 154 | 155 | - name: "3.4.2.8 | AUDIT | Ensure nftables default deny firewall policy | Warn Count" 156 | ansible.builtin.import_tasks: 157 | file: warning_facts.yml 158 | vars: 159 | warn_control_id: '3.4.2.8' 160 | when: 161 | - ubtu20cis_rule_3_4_2_8 162 | - ubtu20cis_firewall_package == "nftables" 163 | tags: 164 | - level1-server 165 | - level1-workstation 166 | - automated 167 | - audit 168 | - rule_3.4.2.8 169 | - nftables 170 | 171 | - name: "3.4.2.9 | AUDIT | Ensure nftables service is enabled" 172 | block: 173 | - name: "3.4.2.9 | AUDIT | Ensure nftables service is enabled" 174 | ansible.builtin.debug: 175 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 176 | 177 | - name: "3.4.2.9 | AUDIT | Ensure nftables service is enabled | Warn Count" 178 | ansible.builtin.import_tasks: 179 | file: warning_facts.yml 180 | vars: 181 | warn_control_id: '3.4.2.9' 182 | when: 183 | - ubtu20cis_rule_3_4_2_9 184 | - ubtu20cis_firewall_package == "nftables" 185 | tags: 186 | - level1-server 187 | - level1-workstation 188 | - automated 189 | - audit 190 | - rule_3.4.2.9 191 | - nftables 192 | 193 | - name: "3.4.2.10 | AUDIT | Ensure nftables rules are permanent" 194 | block: 195 | - name: "3.4.2.10 | AUDIT | Ensure nftables rules are permanent" 196 | ansible.builtin.debug: 197 | msg: "Warning!! NFTables is not supported in this role. Please use UFW, iptables, or manually manage nftables" 198 | 199 | - name: "3.4.2.10 | AUDIT | Ensure nftables rules are permanent | Warn Count" 200 | ansible.builtin.import_tasks: 201 | file: warning_facts.yml 202 | vars: 203 | warn_control_id: '3.4.2.10' 204 | when: 205 | - ubtu20cis_rule_3_4_2_10 206 | - ubtu20cis_firewall_package == "nftables" 207 | tags: 208 | - level1-server 209 | - level1-workstation 210 | - automated 211 | - audit 212 | - rule_3.4.2.10 213 | - nftables 214 | -------------------------------------------------------------------------------- /tasks/section_3/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "SECTION | 3.1 | Disable unused network protocols and devices" 3 | ansible.builtin.import_tasks: 4 | file: cis_3.1.x.yml 5 | 6 | - name: "SECTION | 3.2 | Network Parameters Host Only" 7 | ansible.builtin.import_tasks: 8 | file: cis_3.2.x.yml 9 | 10 | - name: "SECTION | 3.3 | Network Parameters Host and Router" 11 | ansible.builtin.import_tasks: 12 | file: cis_3.3.x.yml 13 | 14 | - name: "SECTION | 3.4.1 | Firewall Configuration UFW" 15 | ansible.builtin.import_tasks: 16 | file: cis_3.4.1.x.yml 17 | when: 18 | - ubtu20cis_firewall_package == "ufw" 19 | 20 | - name: "SECTION | 3.4.2 | Firewall Configuration nftables" 21 | ansible.builtin.import_tasks: 22 | file: cis_3.4.2.x.yml 23 | when: 24 | - ubtu20cis_firewall_package == "nftables" 25 | 26 | - name: "SECTION | 3.4.3 | Firewall Configuration iptables" 27 | ansible.builtin.import_tasks: 28 | file: cis_3.4.3.x.yml 29 | when: 30 | - ubtu20cis_firewall_package == "iptables" 31 | -------------------------------------------------------------------------------- /tasks/section_4/cis_4.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "4.1.1 | PATCH | Ensure cron daemon is enabled and active" 4 | ansible.builtin.service: 5 | name: cron 6 | state: started 7 | enabled: true 8 | when: 9 | - ubtu20cis_rule_4_1_1 10 | tags: 11 | - level1-server 12 | - level1-workstation 13 | - automated 14 | - patch 15 | - rule_4.1.1 16 | - cron 17 | 18 | - name: "4.1.2 | PATCH | Ensure permissions on /etc/crontab are configured" 19 | ansible.builtin.file: 20 | path: /etc/crontab 21 | owner: root 22 | group: root 23 | mode: 0600 24 | when: 25 | - ubtu20cis_rule_4_1_2 26 | tags: 27 | - level1-server 28 | - level1-workstation 29 | - automated 30 | - patch 31 | - rule_4.1.2 32 | - cron 33 | 34 | - name: "4.1.3 | PATCH | Ensure permissions on /etc/cron.hourly are configured" 35 | ansible.builtin.file: 36 | path: /etc/cron.hourly 37 | owner: root 38 | group: root 39 | mode: 0700 40 | when: 41 | - ubtu20cis_rule_4_1_3 42 | tags: 43 | - level1-server 44 | - level1-workstation 45 | - automated 46 | - patch 47 | - rule_4.1.3 48 | - cron 49 | 50 | - name: "4.1.4 | PATCH | Ensure permissions on /etc/cron.daily are configured" 51 | ansible.builtin.file: 52 | path: /etc/cron.daily 53 | owner: root 54 | group: root 55 | mode: 0700 56 | when: 57 | - ubtu20cis_rule_4_1_4 58 | tags: 59 | - level1-server 60 | - level1-workstation 61 | - automated 62 | - patch 63 | - rule_4.1.4 64 | - cron 65 | 66 | - name: "4.1.5 | PATCH | Ensure permissions on /etc/cron.weekly are configured" 67 | ansible.builtin.file: 68 | path: /etc/cron.weekly 69 | owner: root 70 | group: root 71 | mode: 0700 72 | when: 73 | - ubtu20cis_rule_4_1_5 74 | tags: 75 | - level1-server 76 | - level1-workstation 77 | - automated 78 | - patch 79 | - rule_4.1.5 80 | - cron 81 | 82 | - name: "4.1.6 | PATCH | Ensure permissions on /etc/cron.monthly are configured" 83 | ansible.builtin.file: 84 | path: /etc/cron.monthly 85 | owner: root 86 | group: root 87 | mode: 0700 88 | when: 89 | - ubtu20cis_rule_4_1_6 90 | tags: 91 | - level1-server 92 | - level1-workstation 93 | - automated 94 | - patch 95 | - rule_4.1.6 96 | - cron 97 | 98 | - name: "4.1.7 | PATCH | Ensure permissions on /etc/cron.d are configured" 99 | ansible.builtin.file: 100 | path: /etc/cron.d 101 | owner: root 102 | group: root 103 | mode: 0700 104 | when: 105 | - ubtu20cis_rule_4_1_7 106 | tags: 107 | - level1-server 108 | - level1-workstation 109 | - automated 110 | - patch 111 | - rule_4.1.7 112 | - cron 113 | 114 | - name: "4.1.8 | PATCH | Ensure at/cron is restricted to authorized users" 115 | block: 116 | - name: "4.1.8 | PATCH | Ensure at/cron is restricted to authorized users | Remove cron.deny" 117 | ansible.builtin.file: 118 | path: /etc/cron.deny 119 | state: absent 120 | 121 | - name: "4.1.8 | PATCH | Ensure at/cron is restricted to authorized users | Create cron.allow" 122 | ansible.builtin.copy: 123 | content: "" 124 | dest: /etc/cron.allow 125 | force: false 126 | owner: root 127 | group: crontab 128 | mode: 0640 129 | when: 130 | - ubtu20cis_rule_4_1_8 131 | tags: 132 | - level1-server 133 | - level1-workstation 134 | - automated 135 | - patch 136 | - rule_4.1.8 137 | - cron 138 | 139 | - name: "4.1.9 | PATCH | Ensure at is restricted to authorized users" 140 | block: 141 | - name: "4.1.9 | PATCH | Ensure at is restricted to authorized users | Remove at.deny" 142 | ansible.builtin.file: 143 | path: /etc/at.deny 144 | state: absent 145 | 146 | - name: "4.1.9 | PATCH | Ensure at is restricted to authorized users | Create at.allow" 147 | ansible.builtin.copy: 148 | content: "" 149 | dest: /etc/at.allow 150 | force: false 151 | owner: root 152 | group: root 153 | mode: 0640 154 | when: 155 | - ubtu20cis_rule_4_1_9 156 | tags: 157 | - level1-server 158 | - level1-workstation 159 | - automated 160 | - patch 161 | - rule_4.1.9 162 | - cron 163 | -------------------------------------------------------------------------------- /tasks/section_4/cis_4.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "4.3.1 | PATCH | Ensure sudo is installed" 4 | ansible.builtin.package: 5 | name: "{{ ubtu20cis_sudo_package }}" 6 | state: present 7 | when: 8 | - ubtu20cis_rule_4_3_1 9 | tags: 10 | - level1-server 11 | - level1-workstation 12 | - automated 13 | - patch 14 | - rule_4.3.1 15 | - sudo 16 | 17 | - name: "4.3.2 | PATCH | Ensure sudo commands use pty" 18 | ansible.builtin.lineinfile: 19 | path: /etc/sudoers 20 | regexp: '^\s*Defaults\s+use_pty\s*$' 21 | line: 'Defaults use_pty' 22 | insertafter: '^\s*Defaults' 23 | when: 24 | - ubtu20cis_rule_4_3_2 25 | tags: 26 | - level1-server 27 | - level1-workstation 28 | - automated 29 | - patch 30 | - rule_4.3.2 31 | - sudo 32 | 33 | - name: "4.3.3 | PATCH | Ensure sudo log file exists" 34 | ansible.builtin.lineinfile: 35 | path: /etc/sudoers 36 | regexp: '^\s*Defaults\s+logfile\s*=' 37 | line: 'Defaults logfile="{{ ubtu20cis_sudo_logfile }}"' 38 | insertafter: '^\s*Defaults' 39 | when: 40 | - ubtu20cis_rule_4_3_3 41 | tags: 42 | - level1-server 43 | - level1-workstation 44 | - automated 45 | - patch 46 | - rule_4.3.3 47 | - sudo 48 | 49 | - name: "4.3.4 | PATCH | Ensure users must provide password for escalation" 50 | ansible.builtin.replace: 51 | path: "{{ item }}" 52 | regexp: '^([^#|{% if system_is_ec2 %}ec2-user{% endif %}].*)NOPASSWD(.*)' 53 | replace: '\1PASSWD\2' 54 | validate: '/usr/sbin/visudo -cf %s' 55 | loop: "{{ ubtu20cis_sudoers_files.stdout_lines }}" 56 | when: 57 | - ubtu20cis_rule_4_3_4 58 | tags: 59 | - level2-server 60 | - level2-workstation 61 | - patch 62 | - sudo 63 | - rule_4.3.4 64 | 65 | - name: "4.3.5 | PATCH | Ensure re-authentication for privilege escalation is not disabled globally" 66 | ansible.builtin.replace: 67 | path: "{{ item }}" 68 | regexp: '^([^#].*)!authenticate(.*)' 69 | replace: '\1authenticate\2' 70 | validate: '/usr/sbin/visudo -cf %s' 71 | loop: "{{ ubtu20cis_sudoers_files.stdout_lines }}" 72 | when: 73 | - ubtu20cis_rule_4_3_5 74 | tags: 75 | - level1-server 76 | - level1-workstation 77 | - patch 78 | - sudo 79 | - rule_4.3.5 80 | 81 | - name: "4.3.6 | PATCH | Ensure sudo authentication timeout is configured correctly" 82 | block: 83 | - name: "4.3.6 | AUDIT | Ensure sudo authentication timeout is configured correctly | Get files with timeout set" 84 | ansible.builtin.shell: grep -is 'timestamp_timeout' /etc/sudoers /etc/sudoers.d/* | cut -d":" -f1 | uniq | sort 85 | changed_when: false 86 | failed_when: false 87 | register: ubtu20cis_4_3_6_timeout_files 88 | 89 | - name: "4.3.6 | PATCH | Ensure sudo authentication timeout is configured correctly | Set value if no results" 90 | ansible.builtin.lineinfile: 91 | path: /etc/sudoers 92 | regexp: '^\s*Defaults\s+timestamp_timeout\s*=' 93 | line: "Defaults timestamp_timeout={{ ubtu20cis_sudo_timestamp_timeout }}" 94 | validate: '/usr/sbin/visudo -cf %s' 95 | insertafter: '^\s*Defaults' 96 | when: ubtu20cis_4_3_6_timeout_files.stdout | length == 0 97 | 98 | - name: "4.3.6 | PATCH | Ensure sudo authentication timeout is configured correctly | Set value if has results" 99 | ansible.builtin.replace: 100 | path: "{{ item }}" 101 | regexp: 'timestamp_timeout\s*=\s*(\d+)' 102 | replace: "timestamp_timeout={{ ubtu20cis_sudo_timestamp_timeout }}" 103 | validate: '/usr/sbin/visudo -cf %s' 104 | loop: "{{ ubtu20cis_4_3_6_timeout_files.stdout_lines }}" 105 | when: ubtu20cis_4_3_6_timeout_files.stdout | length > 0 106 | when: 107 | - ubtu20cis_rule_4_3_6 108 | tags: 109 | - level1-server 110 | - level1-workstation 111 | - patch 112 | - sudo 113 | - rule_4.3.6 114 | 115 | - name: "4.3.7 | PATCH | Ensure access to the su command is restricted" 116 | block: 117 | 118 | - name: "4.3.7 | PATCH | Ensure access to the su command is restricted | Ensure sugroup exists" 119 | ansible.builtin.group: 120 | name: "{{ ubtu20cis_sugroup }}" 121 | state: present 122 | register: ubtu20cis_4_3_7_sugroup 123 | 124 | - name: "4.3.7 | PATCH | Ensure access to the su command is restricted | remove users from group" 125 | ansible.builtin.lineinfile: 126 | path: /etc/group 127 | regexp: '^{{ ubtu20cis_sugroup }}(:.:.*:).*$' 128 | line: '{{ ubtu20cis_sugroup }}\g<1>' 129 | backrefs: true 130 | 131 | - name: "4.3.7 | PATCH | Ensure access to the su command is restricted | Setting pam_wheel to use_uid" 132 | ansible.builtin.lineinfile: 133 | path: /etc/pam.d/su 134 | regexp: '^(#)?auth\s+required\s+pam_wheel\.so' 135 | line: 'auth required pam_wheel.so use_uid group={{ ubtu20cis_sugroup }}' 136 | when: 137 | - ubtu20cis_rule_4_3_7 138 | tags: 139 | - level1-server 140 | - level1-workstation 141 | - patch 142 | - sudo 143 | - rule_4.3.7 144 | -------------------------------------------------------------------------------- /tasks/section_4/cis_4.5.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "4.5.2 | PATCH | Ensure system accounts are secured" 4 | block: 5 | - name: "4.5.2 | PATCH | Ensure system accounts are secured | Set system accounts to nologin" 6 | ansible.builtin.user: 7 | name: "{{ item }}" 8 | shell: /sbin/nologin 9 | with_items: 10 | - "{{ ubtu20cis_passwd | selectattr('uid', '<', 1000) | map(attribute='id') | list }}" 11 | when: 12 | - item != "root" 13 | - item != "sync" 14 | - item != "shutdown" 15 | - item != "halt" 16 | - item != "irc" 17 | 18 | - name: "4.5.2 | PATCH | Ensure system accounts are secured | Lock non-root system accounts" 19 | ansible.builtin.user: 20 | name: "{{ item }}" 21 | password_lock: true 22 | with_items: 23 | - "{{ ubtu20cis_passwd | selectattr('uid', '<', 1000) | map(attribute='id') | list }}" 24 | when: 25 | - item != "root" 26 | when: 27 | - ubtu20cis_rule_4_5_2 28 | - ubtu20cis_disruption_high 29 | tags: 30 | - level1-server 31 | - level1-workstation 32 | - automated 33 | - patch 34 | - rule_4.5.2 35 | - user 36 | - system 37 | 38 | - name: "4.5.3 | PATCH | Ensure default group for the root account is GID 0" 39 | block: 40 | - name: "4.5.3 | PATCH | Ensure default group for the root account is GID 0 | Set root group to GUID 0" 41 | ansible.builtin.group: 42 | name: root 43 | gid: 0 44 | 45 | - name: "4.5.3 | PATCH | Ensure default group for the root account is GID 0 | Set root user to root group" 46 | ansible.builtin.user: 47 | name: root 48 | group: root 49 | when: 50 | - ubtu20cis_rule_4_5_3 51 | tags: 52 | - level1-server 53 | - level1-workstation 54 | - automated 55 | - patch 56 | - rule_4.5.3 57 | - user 58 | - system 59 | 60 | - name: "4.5.4 | PATCH | Ensure default user umask is 027 or more restrictive" 61 | block: 62 | - name: "4.5.4 | AUDIT | Ensure default user umask is 027 or more restrictive" 63 | ansible.builtin.shell: grep -E '^session\s+optional\s+pam_umask.so' /etc/pam.d/common-session 64 | changed_when: false 65 | failed_when: false 66 | check_mode: false 67 | register: ubtu20cis_4_5_4_umask_pam_status 68 | 69 | - name: "4.5.4 | PATCH | Ensure default user umask is 027 or more restrictive" 70 | ansible.builtin.lineinfile: 71 | path: /etc/pam.d/common-session 72 | line: 'session optional pam_umask.so' 73 | insertbefore: '^# end of pam-auth-update config' 74 | when: ubtu20cis_4_5_4_umask_pam_status.stdout | length == 0 75 | 76 | - name: "4.5.4 | PATCH | Ensure default user umask is 027 or more restrictive" 77 | ansible.builtin.lineinfile: 78 | path: "{{ item.path }}" 79 | regexp: '(?i)(umask\s*\d\d\d)' 80 | line: '{{ item.line }} {{ ubtu20cis_bash_umask }}' 81 | with_items: 82 | - { path: '/etc/bash.bashrc', line: 'umask' } 83 | - { path: '/etc/profile', line: 'umask' } 84 | - { path: '/etc/login.defs', line: 'UMASK' } 85 | 86 | - name: "4.5.4 | PATCH | Ensure default user umask is 027 or more restrictive" 87 | ansible.builtin.lineinfile: 88 | path: /etc/login.defs 89 | regexp: '^USERGROUPS_ENAB' 90 | line: USERGROUPS_ENAB no 91 | when: 92 | - ubtu20cis_rule_4_5_4 93 | tags: 94 | - level1-server 95 | - level1-workstation 96 | - automated 97 | - patch 98 | - rule_4.5.4 99 | - user 100 | 101 | - name: "4.5.5 | PATCH | Ensure default user shell timeout is configured" 102 | ansible.builtin.blockinfile: 103 | create: true 104 | mode: 0644 105 | dest: "{{ item.dest }}" 106 | state: "{{ item.state }}" 107 | marker: "# {mark} ANSIBLE MANAGED" 108 | block: | 109 | # Set session timeout - CIS ID 4.5.5 110 | # only set TMOUT if it isn't set yet to avoid a shell error 111 | : ${TMOUT={{ ubtu20cis_shell_session_timeout.timeout }}} 112 | readonly TMOUT 113 | export TMOUT 114 | with_items: 115 | - { dest: "{{ ubtu20cis_shell_session_timeout.file }}", state: present } 116 | - { dest: /etc/profile, state: "{{ (ubtu20cis_shell_session_timeout.file == '/etc/profile') | ternary('present', 'absent') }}" } 117 | - { dest: /etc/bash.bashrc, state: present } 118 | when: 119 | - ubtu20cis_rule_4_5_5 120 | tags: 121 | - level1-server 122 | - level1-workstation 123 | - automated 124 | - patch 125 | - rule_4.5.5 126 | - user 127 | 128 | - name: "4.5.6 | PATCH | Ensure nologin is not listed in /etc/shells" 129 | ansible.builtin.lineinfile: 130 | path: /etc/shells 131 | regex: nologin\b 132 | state: absent 133 | when: 134 | - ubtu20cis_rule_4_5_6 135 | tags: 136 | - level2-server 137 | - level2-workstation 138 | - automated 139 | - patch 140 | - rule_4.5.6 141 | - user 142 | 143 | - name: "4.5.7 | PATCH | Ensure maximum number of same consecutive characters in a password is configured" 144 | ansible.builtin.lineinfile: 145 | path: /etc/security/pwquality.conf 146 | regexp: '^(#\s+|)maxrepeat' 147 | line: 'maxrepeat = {{ ubtu20cis_pass.max_repeat_letters }}' 148 | when: 149 | - ubtu20cis_rule_4_5_7 150 | tags: 151 | - level1-server 152 | - level1-workstation 153 | - automated 154 | - patch 155 | - rule_4.5.7 156 | - user 157 | - login 158 | -------------------------------------------------------------------------------- /tasks/section_4/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "SECTION | 4.1 | Configure job based time schedulers" 3 | ansible.builtin.import_tasks: 4 | file: cis_4.1.x.yml 5 | 6 | - name: "SECTION | 4.2 | Configure SSH Server" 7 | ansible.builtin.import_tasks: 8 | file: cis_4.2.x.yml 9 | 10 | - name: "SECTION | 4.3 | Configure Privilege escalations" 11 | ansible.builtin.import_tasks: 12 | file: cis_4.3.x.yml 13 | 14 | - name: "SECTION | 4.4 | Configure PAM" 15 | ansible.builtin.import_tasks: 16 | file: cis_4.4.x.yml 17 | 18 | - name: "SECTION | 4.5.1.x | User Accounts and Environment | Shadow Suite" 19 | ansible.builtin.import_tasks: 20 | file: cis_4.5.1.x.yml 21 | 22 | - name: "SECTION | 4.5.x | User Accounts and Environment | password params" 23 | ansible.builtin.import_tasks: 24 | file: cis_4.5.x.yml 25 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.1.1.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.1.1.1.1 | Ensure systemd-journal-remote is installed" 4 | ansible.builtin.package: 5 | name: systemd-journal-remote 6 | state: present 7 | when: 8 | - ubtu20cis_rule_5_1_1_1_1 9 | tags: 10 | - level1-server 11 | - level1-workstation 12 | - automated 13 | - patch 14 | - rule_5.1.1.1.1 15 | - journald 16 | 17 | - name: "5.1.1.1.2 | PATCH | Ensure systemd-journal-remote is configured" 18 | ansible.builtin.lineinfile: 19 | path: /etc/systemd/journal-upload.conf 20 | regexp: "{{ item.regexp }}" 21 | line: "{{ item.line }}" 22 | insertafter: "{{ item.after | default (omit) }}" 23 | notify: restart journal-upload 24 | loop: 25 | - { regexp: 'URL=', line: 'URL={{ ubtu20cis_journald_upload.remote_url }}' } 26 | - { regexp: 'ServerKeyFile=', line: 'ServerKeyFile={{ ubtu20cis_journal_upload.serverkeyfile }}' } 27 | - { regexp: 'ServerCertificateFile=', line: 'ServerKeyFile={{ ubtu20cis_journal_upload.servercertfile }}' } 28 | - { regexp: 'TrustedCertificateFile=', line: 'ServerKeyFile={{ ubtu20cis_journal_upload.trustfile }}' } 29 | when: 30 | - ubtu20cis_rule_5_1_1_1_2 31 | tags: 32 | - level1-server 33 | - level1-workstation 34 | - automated 35 | - patch 36 | - rule_5.1.1.1.2 37 | - journald 38 | 39 | - name: "5.1.1.1.3 | PATCH | Ensure systemd-journal-remote is enabled" 40 | ansible.builtin.systemd: 41 | name: systemd-journal-upload.service 42 | state: started 43 | enabled: true 44 | when: 45 | - ubtu20cis_rule_5_1_1_1_3 46 | tags: 47 | - level1-server 48 | - level1-workstation 49 | - automated 50 | - patch 51 | - rule_5.1.1.1.3 52 | - journald 53 | 54 | - name: "5.1.1.1.4 | Ensure journald is not configured to receive logs from a remote client" 55 | ansible.builtin.systemd: 56 | name: systemd-journal-remote 57 | state: stopped 58 | enabled: false 59 | when: 60 | - ubtu20cis_rule_5_1_1_1_4 61 | - not journald_log_server 62 | tags: 63 | - level1-server 64 | - level1-workstation 65 | - automated 66 | - patch 67 | - rule_5.1.1.1.4 68 | - journald 69 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.1.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.1.1.2 | Ensure journald service is enabled" 4 | block: 5 | - name: "5.1.1.2 | Ensure journald service is enabled | capture status" 6 | ansible.builtin.shell: systemctl is-enabled systemd-journald.service 7 | register: ubtu20cis_5_1_1_2_journald_enabled 8 | changed_when: false 9 | failed_when: ubtu20cis_5_1_1_2_journald_enabled.rc not in [ 0, 1, 2 ] 10 | 11 | - name: "5.1.1.2 | Ensure journald service is enabled | warn count if not as expected" 12 | ansible.builtin.import_tasks: 13 | file: warning_facts.yml 14 | when: "'static' not in ubtu20cis_5_1_1_2_journald_enabled.stdout" 15 | vars: 16 | warn_control_id: '5.1.1.2' 17 | when: 18 | - ubtu20cis_rule_5_1_1_2 19 | tags: 20 | - level1-server 21 | - level1-workstation 22 | - automated 23 | - patch 24 | - rule_5.1.1.2 25 | - journald 26 | 27 | - name: "5.1.1.3 | PATCH | Ensure journald is configured to compress large log files" 28 | ansible.builtin.lineinfile: 29 | path: /etc/systemd/journald.conf 30 | regexp: '^Compress|^#Compress' 31 | line: 'Compress=yes' 32 | insertafter: '\[Journal\]' 33 | when: 34 | - ubtu20cis_rule_5_1_1_3 35 | tags: 36 | - level1-server 37 | - level1-workstation 38 | - patch 39 | - rule_5.1.1.3 40 | - journald 41 | 42 | - name: "5.1.1.4 | PATCH | Ensure journald is configured to write logfiles to persistent disk" 43 | ansible.builtin.lineinfile: 44 | path: /etc/systemd/journald.conf 45 | regexp: '^Storage|^#Storage' 46 | line: 'Storage=persistent' 47 | insertafter: '\[Journal\]' 48 | when: 49 | - ubtu20cis_rule_5_1_1_4 50 | tags: 51 | - level1-server 52 | - level1-workstation 53 | - automated 54 | - patch 55 | - rule_5.1.1.4 56 | - journald 57 | 58 | - name: "5.1.1.5 | PATCH | Ensure journald is not configured to send logs to rsyslog" 59 | ansible.builtin.lineinfile: 60 | path: /etc/systemd/journald.conf 61 | regexp: '^ForwardToSyslog|^#ForwardToSyslog' 62 | line: 'ForwardToSyslog=no' 63 | insertafter: '\[Journal\]' 64 | when: 65 | - ubtu20cis_rule_5_1_1_5 66 | tags: 67 | - level1-server 68 | - level1-workstation 69 | - automated 70 | - patch 71 | - rule_5.1.1.5 72 | - journald 73 | 74 | - name: "5.1.1.6 | PATCH | Ensure journald log rotation is configured per site policy" 75 | ansible.builtin.lineinfile: 76 | path: /etc/systemd/journald.conf 77 | regexp: "{{ item.regexp }}" 78 | line: "{{ item.line }}" 79 | loop: 80 | - { regexp: 'SystemMaxUse=', line: "SystemMaxUse={{ ubtu20cis_journald_log_rotate.systemmaxuse }}" } 81 | - { regexp: 'SystemKeepFree=', line: "SystemKeepFree={{ ubtu20cis_journald_log_rotate.systemkeepfree }}" } 82 | - { regexp: 'RunTimeMaxUse=', line: "RunTimeMaxUse={{ ubtu20cis_journald_log_rotate.runtimemaxuse }}" } 83 | - { regexp: 'RuntimeKeepFree=', line: "RuntimeKeepFree={{ ubtu20cis_journald_log_rotate.runtimekeepfree }}" } 84 | - { regexp: 'MaxFileSec=', line: "MaxFileSec={{ ubtu20cis_journald_log_rotate.maxfilesec }}" } 85 | when: 86 | - ubtu20cis_rule_5_1_1_6 87 | tags: 88 | - level1-server 89 | - level1-workstation 90 | - automated 91 | - patch 92 | - rule_5.1.1.6 93 | - journald 94 | 95 | - name: "5.1.1.7 | PATCH | Ensure journald default file permissions configured" 96 | block: 97 | - name: "5.1.1.7 | AUDIT | Ensure journald default file permissions configured | check for override file" 98 | ansible.builtin.stat: 99 | path: /etc/tmpfiles.d/systemd.conf 100 | register: ubtu20cis_5_1_1_7_tmpfile 101 | 102 | - name: "5.1.1.7 | AUDIT | Ensure journald default file permissions configured | update settings if override exists" 103 | ansible.builtin.lineinfile: 104 | path: /etc/tmpfiles.d/systemd.conf 105 | regexp: "{{ item.line }}" 106 | line: "{{ item.line }}" 107 | backrefs: true 108 | when: ubtu20cis_5_1_1_7_tmpfile.stat.exists 109 | loop: 110 | - { regexp: '.*system.journal.*(:r(w|-)(x|-))(,|$)', line: 'a+ /var/log/journal/%m/system.journal - - - - group:adm:r--'} 111 | - { regexp: '.*\/var\/log\/journal\/\%m\s.*(:r(w|-)(x|-))(,|$)', line: 'a+ /var/log/journal/%m - - - - d:group:adm:r-x,group:adm:r-x'} 112 | - { regexp: '.*\/var\/log\/journal\s.*(:r(w|-)(x|-))(,|$)', line: 'a+ /var/log/journal - - - - d:group::r-x,d:group:adm:r-x,group::r-x,group:adm:r-x'} 113 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.1.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "5.1.2.1 | PATCH | Ensure rsyslog is installed" 3 | ansible.builtin.package: 4 | name: rsyslog 5 | state: present 6 | when: 7 | - ubtu20cis_rule_5_1_2_1 8 | tags: 9 | - level1-server 10 | - level1-workstation 11 | - automated 12 | - patch 13 | - rule_5.1.2.1 14 | - rsyslog 15 | - apt 16 | 17 | - name: "5.1.2.2 | PATCH | Ensure rsyslog Service is enabled" 18 | ansible.builtin.service: 19 | name: rsyslog 20 | enabled: true 21 | when: 22 | - ubtu20cis_rule_5_1_2_2 23 | tags: 24 | - level1-server 25 | - level1-workstation 26 | - automated 27 | - patch 28 | - rule_5.1.2.2 29 | - rsyslog 30 | 31 | - name: "5.1.2.3 | PATCH | Ensure journald is configured to send logs to rsyslog" 32 | ansible.builtin.lineinfile: 33 | path: /etc/systemd/journald.conf 34 | regexp: ^ForwardToSyslog= 35 | line: ForwardToSyslog=yes 36 | notify: restart syslog service 37 | when: 38 | - ubtu20cis_rule_5_1_2_3 39 | tags: 40 | - level1-server 41 | - level1-workstation 42 | - manual 43 | - patch 44 | - journald 45 | - rule_5.1.2.3 46 | 47 | - name: "5.1.2.4 | PATCH | Ensure rsyslog default file permissions configured" 48 | ansible.builtin.lineinfile: 49 | path: /etc/rsyslog.conf 50 | regexp: '^\$FileCreateMode|^#\$FileCreateMode' 51 | line: '$FileCreateMode 0640' 52 | notify: restart syslog service 53 | when: 54 | - ubtu20cis_rule_5_1_2_4 55 | tags: 56 | - level1-server 57 | - level1-workstation 58 | - automated 59 | - patch 60 | - rule_5.1.2.4 61 | - rsyslog 62 | 63 | - name: "5.1.2.5 | PATCH | Ensure logging is configured" 64 | block: 65 | - name: "5.1.2.5 | AUDIT | Ensure logging is configured | Find configuration file" 66 | ansible.builtin.shell: grep -r "*.emerg" /etc/* | cut -f1 -d":" 67 | changed_when: false 68 | failed_when: false 69 | check_mode: false 70 | register: ubtu20cis_5_1_2_5_rsyslog_config_path 71 | 72 | - name: "5.1.2.5 | AUDIT | Ensure logging is configured | Gather rsyslog current config" 73 | ansible.builtin.shell: "cat {{ ubtu20cis_5_1_2_5_rsyslog_config_path.stdout }}" 74 | changed_when: false 75 | failed_when: false 76 | check_mode: false 77 | register: ubtu20cis_5_1_2_5_rsyslog_config 78 | 79 | - name: "5.1.2.5 | AUDIT | Ensure logging is configured | Message out config" 80 | ansible.builtin.debug: 81 | msg: 82 | - "Warning!! Below is the current logging configurations for rsyslog, please review" 83 | - "{{ ubtu20cis_5_1_2_5_rsyslog_config.stdout_lines }}" 84 | when: not ubtu20cis_rsyslog_ansible_managed 85 | 86 | - name: "5.1.2.5 | PATCH | Ensure logging is configured | Automated rsyslog configuration" 87 | ansible.builtin.lineinfile: 88 | path: "{{ ubtu20cis_5_1_2_5_rsyslog_config_path.stdout }}" 89 | regexp: "{{ item.regexp }}" 90 | line: "{{ item.line }}" 91 | insertafter: "{{ item.insertafter }}" 92 | create: true 93 | with_items: 94 | - { regexp: '^\*.emerg', line: '*.emerg :omusrmsg:*', insertafter: '^# Emergencies are sent to everybody logged in' } 95 | - { regexp: '^auth,authpriv.\*', line: 'auth,authpriv.* /var/log/auth.log', insertafter: '^# First some standard log files. Log by facility' } 96 | - { regexp: '^mail.\*|^#mail.\*', line: 'mail.* -/var/log/mail', insertafter: '^# First some standard log files' } 97 | - { regexp: '^mail.info|^#mail.info', line: 'mail.info -/var/log/mail.info', insertafter: '^# Logging for the mail system' } 98 | - { regexp: '^mail.warn|^#mail.warn', line: 'mail.warn -/var/log/mail.warn', insertafter: '^# Logging for the mail system.' } 99 | - { regexp: '^mail.err|^#mail.err', line: 'mail.err /var/log/mail.err', insertafter: '^# Logging for the mail system.' } 100 | - { regexp: '^cron.\*|^#cron.\*', line: 'cron.* /var/log/cron', insertafter: '^# First some standard log files' } 101 | - { regexp: '^\*.=warning;\*.=err|^#\*.=warning;\*.=err', line: '*.=warning;*.=err -/var/log/warn', insertafter: '^# First some standard log files' } 102 | - { regexp: '^\*.crit|^#\*.crit', line: '*.crit /var/log/warn', insertafter: '^# First some standard log files' } 103 | - { regexp: '^\*.\*;mail.none;news.none|^#\*.\*;mail.none;news.none', line: '*.*;mail.none;news.none -/var/log/messages', insertafter: '^# First some standard log files' } 104 | - { regexp: '^local0,local1.\*|^#local0,local1.\*', line: 'local0,local1.* -/var/log/localmessages', insertafter: '^# First some standard log files' } 105 | - { regexp: '^local2,local3.\*|^#local2,local3.\*', line: 'local2,local3.* -/var/log/localmessages', insertafter: '^# First some standard log files' } 106 | - { regexp: '^local4,local5.\*|^#local4,local5.\*', line: 'local4,local5.* -/var/log/localmessages', insertafter: '^# First some standard log files' } 107 | - { regexp: '^local6,local7.\*|^#local6,local7.\*', line: 'local6,local7.* -/var/log/localmessages', insertafter: '^# First some standard log files' } 108 | loop_control: 109 | label: "{{ item.line }}" 110 | notify: restart syslog service 111 | when: ubtu20cis_rsyslog_ansible_managed 112 | 113 | - name: "5.1.2.5 | AUDIT | Ensure logging is configured | Warn Count" 114 | ansible.builtin.import_tasks: 115 | file: warning_facts.yml 116 | when: not ubtu20cis_rsyslog_ansible_managed 117 | vars: 118 | warn_control_id: '5.1.2.5' 119 | when: 120 | - ubtu20cis_rule_5_1_2_5 121 | tags: 122 | - level1-server 123 | - level1-workstation 124 | - manual 125 | - patch 126 | - rule_5.1.2.5 127 | - rsyslog 128 | 129 | - name: "5.1.2.6 | PATCH | Ensure rsyslog is configured to send logs to a remote log host" 130 | ansible.builtin.blockinfile: 131 | path: /etc/rsyslog.conf 132 | block: | 133 | ##Enable sending of logs over TCP add the following line: 134 | *.* @@{{ ubtu20cis_remote_log_server }} 135 | insertafter: EOF 136 | when: 137 | - ubtu20cis_rule_5_1_2_6 138 | tags: 139 | - level1-server 140 | - level1-workstation 141 | - automated 142 | - patch 143 | - rule_5.1.2.6 144 | - rsyslog 145 | 146 | - name: "5.1.2.7 | PATCH | Ensure rsyslog is not configured to receive logs from a remote client | When not a log host" 147 | ansible.builtin.replace: 148 | path: /etc/rsyslog.conf 149 | regexp: '({{ item }})' 150 | replace: '#\1' 151 | with_items: 152 | - '^(\$ModLoad)' 153 | - '^(\$InputTCPServerRun)' 154 | - '^(module\(load="itcmp"\))' 155 | - '^(input\(type="imtcp" port="\d{1,5}"\))' 156 | notify: restart syslog service 157 | when: 158 | - not ubtu20cis_system_is_log_server 159 | - ubtu20cis_rule_5_1_2_7 160 | tags: 161 | - level1-server 162 | - level1-workstation 163 | - manual 164 | - patch 165 | - rule_5.1.2.7 166 | - rsyslog 167 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.1.3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.1.3 | PATCH | Ensure permissions on all logfiles are configured" 4 | block: 5 | - name: "5.1.3 | AUDIT | Ensure permissions on all logfiles are configured | find files" 6 | ansible.builtin.find: 7 | paths: "/var/log" 8 | file_type: file 9 | recurse: true 10 | register: logfiles 11 | 12 | - name: "5.1.3 | PATCH | Ensure permissions on all logfiles are configured | change permissions" 13 | ansible.builtin.file: 14 | path: "{{ item.path }}" 15 | mode: 0640 16 | loop: "{{ logfiles.files }}" 17 | loop_control: 18 | label: "{{ item.path }}" 19 | when: 20 | - item.path != "/var/log/btmp" 21 | - item.path != "/var/log/utmp" 22 | - item.path != "/var/log/wtmp" 23 | when: 24 | - ubtu20cis_rule_5_1_3 25 | tags: 26 | - level1-server 27 | - level1-workstation 28 | - patch 29 | - logfiles 30 | - rule_5.1.3 31 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.2.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "5.2.1.1 | PATCH | Ensure auditd is installed" 3 | ansible.builtin.package: 4 | name: ['auditd', 'audispd-plugins'] 5 | state: present 6 | when: 7 | - ubtu20cis_rule_5_2_1_1 8 | tags: 9 | - level2-server 10 | - level2-workstation 11 | - automated 12 | - patch 13 | - rule_5.2.1.1 14 | - auditd 15 | 16 | - name: "5.2.1.2 | PATCH | Ensure auditd service is enabled and active" 17 | ansible.builtin.service: 18 | name: auditd 19 | state: started 20 | enabled: true 21 | when: 22 | - ubtu20cis_rule_5_2_1_2 23 | tags: 24 | - level2-server 25 | - level2-workstation 26 | - automated 27 | - patch 28 | - rule_5.2.1.2 29 | - auditd 30 | 31 | - name: "5.2.1.3 | PATCH | Ensure auditing for processes that start prior to auditd is enabled" 32 | block: 33 | - name: "5.2.1.3 | AUDIT | Ensure auditing for processes that start prior to auditd is enabled | Get GRUB_CMDLINE_LINUX" 34 | ansible.builtin.shell: grep "GRUB_CMDLINE_LINUX=" /etc/default/grub | cut -f2 -d'"' 35 | changed_when: false 36 | failed_when: false 37 | check_mode: false 38 | register: ubtu20cis_5_2_1_3_cmdline_settings 39 | 40 | - name: "5.2.1.3 | PATCH | Ensure auditing for processes that start prior to auditd is enabled | Add setting if doesn't exist" 41 | ansible.builtin.lineinfile: 42 | path: /etc/default/grub 43 | regexp: '^GRUB_CMDLINE_LINUX=' 44 | line: 'GRUB_CMDLINE_LINUX="{{ ubtu20cis_5_2_1_3_cmdline_settings.stdout }} audit=1"' 45 | when: "'audit=' not in ubtu20cis_5_2_1_3_cmdline_settings.stdout" 46 | notify: Grub update 47 | 48 | - name: "5.2.1.3 | PATCH | Ensure auditing for processes that start prior to auditd is enabled | Update setting if exists" 49 | ansible.builtin.replace: 50 | dest: /etc/default/grub 51 | regexp: 'audit=([0-9]+)' 52 | replace: 'audit=1' 53 | after: '^GRUB_CMDLINE_LINUX="' 54 | before: '"' 55 | notify: Grub update 56 | when: "'audit=' in ubtu20cis_5_2_1_3_cmdline_settings.stdout" 57 | when: 58 | - ubtu20cis_rule_5_2_1_3 59 | tags: 60 | - level2-server 61 | - level2-workstation 62 | - automated 63 | - patch 64 | - rule_5_2_1_3 65 | - auditd 66 | 67 | - name: "5.2.1.4 | PATCH | Ensure audit_backlog_limit is sufficient" 68 | block: 69 | - name: "5.2.1.4 | PATCH | Ensure audit_backlog_limit is sufficient | Get current GRUB_CMDLINE_LINUX" 70 | ansible.builtin.shell: grep "GRUB_CMDLINE_LINUX=" /etc/default/grub | cut -f2 -d'"' 71 | changed_when: false 72 | failed_when: false 73 | check_mode: false 74 | register: ubtu20cis_5_2_1_4_cmdline_settings 75 | 76 | - name: "5.2.1.4 | PATCH | Ensure audit_backlog_limit is sufficient | Add setting if doesn't exist" 77 | ansible.builtin.lineinfile: 78 | path: /etc/default/grub 79 | regexp: '^GRUB_CMDLINE_LINUX=' 80 | line: 'GRUB_CMDLINE_LINUX="{{ ubtu20cis_5_2_1_4_cmdline_settings.stdout }} audit_backlog_limit={{ ubtu20cis_audit_back_log_limit }}"' 81 | notify: Grub update 82 | when: "'audit_backlog_limit=' not in ubtu20cis_5_2_1_4_cmdline_settings.stdout" 83 | 84 | - name: "5.2.1.4 | PATCH | Ensure audit_backlog_limit is sufficient | Update setting if exists" 85 | ansible.builtin.replace: 86 | dest: /etc/default/grub 87 | regexp: 'audit_backlog_limit=([0-9]+)' 88 | replace: 'audit_backlog_limit={{ ubtu20cis_audit_back_log_limit }}' 89 | after: '^GRUB_CMDLINE_LINUX="' 90 | before: '"' 91 | notify: Grub update 92 | when: 93 | - ubtu20cis_rule_5_2_1_4 94 | tags: 95 | - level2-server 96 | - level2-workstation 97 | - automated 98 | - patch 99 | - rule_5.2.1.4 100 | - auditd 101 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.2.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.2.2.1 | PATCH | Ensure audit log storage size is configured" 4 | ansible.builtin.lineinfile: 5 | path: /etc/audit/auditd.conf 6 | regexp: "^max_log_file( |=)" 7 | line: "max_log_file = {{ ubtu20cis_max_log_file_size }}" 8 | state: present 9 | notify: restart auditd 10 | when: 11 | - ubtu20cis_rule_5_2_2_1 12 | tags: 13 | - level2-server 14 | - level2-workstation 15 | - automated 16 | - patch 17 | - rule_5.2.2.1 18 | - auditd 19 | 20 | - name: "5.2.2.2 | PATCH | Ensure audit logs are not automatically deleted" 21 | ansible.builtin.lineinfile: 22 | path: /etc/audit/auditd.conf 23 | regexp: '^max_log_file_action' 24 | line: "max_log_file_action = {{ ubtu20cis_auditd['max_log_file_action'] }}" 25 | notify: restart auditd 26 | when: 27 | - ubtu20cis_rule_5_2_2_2 28 | tags: 29 | - level2-server 30 | - level2-workstation 31 | - automated 32 | - patch 33 | - rule_5.2.2.2 34 | - auditd 35 | 36 | - name: "5.2.2.3 | PATCH | Ensure system is disabled when audit logs are full" 37 | ansible.builtin.lineinfile: 38 | path: /etc/audit/auditd.conf 39 | regexp: "{{ item.regexp }}" 40 | line: "{{ item.line }}" 41 | with_items: 42 | - { regexp: '^space_left_action', line: 'space_left_action = email' } 43 | - { regexp: '^action_mail_acct', line: 'action_mail_acct = root' } 44 | - { regexp: '^admin_space_left_action', line: "admin_space_left_action = {{ ubtu20cis_auditd['admin_space_left_action'] }}" } 45 | notify: restart auditd 46 | when: 47 | - ubtu20cis_rule_5_2_2_3 48 | tags: 49 | - level2-server 50 | - level2-workstation 51 | - automated 52 | - patch 53 | - rule_5.2.2.3 54 | - auditd 55 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.2.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: | 4 | "5.2.4.1 | PATCH | Ensure audit log files are mode 0640 or less permissive" 5 | "5.2.4.2 | PATCH | Ensure only authorized users own audit log files" 6 | "5.2.4.3 | PATCH | Ensure only authorized groups are assigned ownership of audit log files" 7 | 8 | block: 9 | - name: "5.2.4.1 | AUDIT | Ensure audit log files are mode 0640 or less permissive | discover file" 10 | ansible.builtin.shell: grep ^log_file /etc/audit/auditd.conf | awk '{ print $NF }' 11 | changed_when: false 12 | register: audit_discovered_logfile 13 | 14 | - name: "5.2.4.1 | AUDIT | Ensure audit log files are mode 0640 or less permissive | stat file" 15 | ansible.builtin.stat: 16 | path: "{{ audit_discovered_logfile.stdout }}" 17 | changed_when: false 18 | register: auditd_logfile 19 | 20 | - name: | 21 | "5.2.4.1 | PATCH | Ensure audit log files are mode 0640 or less permissive" 22 | "5.2.4.2 | PATCH | Ensure only authorized users own audit log files" 23 | "5.2.4.3 | PATCH | Ensure only authorized groups are assigned ownership of audit log files" 24 | ansible.builtin.file: 25 | path: "{{ audit_discovered_logfile.stdout }}" 26 | mode: "{% if auditd_logfile.stat.mode > '0640' %}0640{% endif %}" 27 | owner: root 28 | group: root 29 | when: 30 | - ubtu20cis_rule_5_2_4_1 or 31 | ubtu20cis_rule_5_2_4_2 or 32 | ubtu20cis_rule_5_2_4_3 33 | tags: 34 | - level2-server 35 | - level2-workstation 36 | - patch 37 | - auditd 38 | - rule_5.2.4.1 39 | - rule_5.2.4.2 40 | - rule_5.2.4.3 41 | 42 | - name: "5.2.4.4 | PATCH | Ensure the audit log directory is 0750 or more restrictive" 43 | block: 44 | - name: "5.2.4.4 | AUDIT | Ensure the audit log directory is 0750 or more restrictive | get current permissions" 45 | ansible.builtin.stat: 46 | path: "{{ audit_discovered_logfile.stdout | dirname }}" 47 | register: auditlog_dir 48 | 49 | - name: "5.2.4.4 | PATCH | Ensure the audit log directory is 0750 or more restrictive | set" 50 | ansible.builtin.file: 51 | path: "{{ audit_discovered_logfile.stdout | dirname }}" 52 | state: directory 53 | mode: 0750 54 | when: not auditlog_dir.stat.mode is match('07(0|5)0') 55 | when: 56 | - ubtu20cis_rule_5_2_4_4 57 | tags: 58 | - level2-server 59 | - level2-workstation 60 | - patch 61 | - auditd 62 | - rule_5.2.4.4 63 | 64 | - name: "5.2.4.5 | PATCH | Ensure audit configuration files are 640 or more restrictive" 65 | ansible.builtin.file: 66 | path: "{{ item.path }}" 67 | mode: '0640' 68 | loop: "{{ auditd_conf_files.files|default([])}}" 69 | loop_control: 70 | label: "{{ item.path }}" 71 | when: 72 | - item.mode > '0640' 73 | - ubtu20cis_rule_5_2_4_5 74 | tags: 75 | - level2-server 76 | - level2-workstation 77 | - patch 78 | - auditd 79 | - rule_5.2.4.5 80 | 81 | - name: "5.2.4.6 | PATCH | Ensure audit configuration files are owned by root" 82 | ansible.builtin.file: 83 | path: "{{ item.path }}" 84 | owner: root 85 | loop: "{{ auditd_conf_files.files }}" 86 | loop_control: 87 | label: "{{ item.path }}" 88 | when: 89 | - ubtu20cis_rule_5_2_4_6 90 | tags: 91 | - level2-server 92 | - level2-workstation 93 | - patch 94 | - auditd 95 | - rule_5.2.4.6 96 | 97 | - name: "5.2.4.7 | PATCH | Ensure audit configuration files belong to group root" 98 | ansible.builtin.file: 99 | path: "{{ item.path }}" 100 | group: root 101 | loop: "{{ auditd_conf_files.files }}" 102 | loop_control: 103 | label: "{{ item.path }}" 104 | when: 105 | - ubtu20cis_rule_5_2_4_7 106 | tags: 107 | - level2-server 108 | - level2-workstation 109 | - patch 110 | - auditd 111 | - rule_5.2.4.7 112 | 113 | - name: "5.2.4.8 | PATCH | Ensure audit tools are 755 or more restrictive" 114 | block: 115 | - name: "5.2.4.8 | AUDIT | Get audit binary file stat | get current mode" 116 | ansible.builtin.stat: 117 | path: "{{ item }}" 118 | register: "audit_bins" 119 | loop: 120 | - /sbin/auditctl 121 | - /sbin/aureport 122 | - /sbin/ausearch 123 | - /sbin/autrace 124 | - /sbin/auditd 125 | - /sbin/augenrules 126 | 127 | - name: "5.2.4.8 | PATCH | Ensure audit tools are 755 or more restrictive | set if required" 128 | ansible.builtin.file: 129 | path: "{{ item.item }}" 130 | mode: 0750 131 | 132 | loop: "{{ audit_bins.results }}" 133 | loop_control: 134 | label: "{{ item.item }}" 135 | when: not item.stat.mode is match('07(0|5)0') 136 | when: 137 | - ubtu20cis_rule_5_2_4_8 138 | tags: 139 | - level2-server 140 | - level2-workstation 141 | - patch 142 | - auditd 143 | - rule_5.2.4.8 144 | 145 | - name: "5.2.4.9 | PATCH | Ensure audit tools are owned by root" 146 | ansible.builtin.file: 147 | path: "{{ item }}" 148 | owner: root 149 | group: root 150 | loop: 151 | - /sbin/auditctl 152 | - /sbin/aureport 153 | - /sbin/ausearch 154 | - /sbin/autrace 155 | - /sbin/auditd 156 | - /sbin/augenrules 157 | when: 158 | - ubtu20cis_rule_5_2_4_9 159 | tags: 160 | - level2-server 161 | - level2-workstation 162 | - patch 163 | - auditd 164 | - rule_5.2.4.9 165 | 166 | - name: "5.2.4.10 | PATCH | Ensure audit tools belong to group root" 167 | ansible.builtin.file: 168 | path: "{{ item }}" 169 | group: root 170 | loop: 171 | - /sbin/auditctl 172 | - /sbin/aureport 173 | - /sbin/ausearch 174 | - /sbin/autrace 175 | - /sbin/auditd 176 | - /sbin/augenrules 177 | when: 178 | - ubtu20cis_rule_5_2_4_10 179 | tags: 180 | - level2-server 181 | - level2-workstation 182 | - patch 183 | - auditd 184 | - rule_5.2.4.10 185 | 186 | - name: "5.2.4.11 | PATCH | Ensure cryptographic mechanisms are used to protect the integrity of audit tools" 187 | ansible.builtin.lineinfile: 188 | path: /etc/aide/aide.conf 189 | regexp: "{{ item }}" 190 | line: "{{ item }}" 191 | loop: 192 | - '# Audit tools' 193 | - /sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512 194 | - /sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512 195 | - /sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512 196 | - /sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512 197 | - /sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512 198 | - /sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512 199 | when: 200 | - ubtu20cis_rule_5_2_4_11 201 | tags: 202 | - level2-server 203 | - level2-workstation 204 | - patch 205 | - auditd 206 | - rule_5.2.4.11 207 | -------------------------------------------------------------------------------- /tasks/section_5/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "SECTION | 5.1.1.1 | Configure journald remote" 3 | ansible.builtin.import_tasks: 4 | file: cis_5.1.1.1.x.yml 5 | when: 6 | - ubtu20cis_syslog_service == 'journald' 7 | 8 | - name: "SECTION | 5.1.1 | Configure journald remote" 9 | ansible.builtin.import_tasks: 10 | file: cis_5.1.1.x.yml 11 | when: 12 | - ubtu20cis_syslog_service == 'journald' 13 | 14 | - name: "SECTION | 5.1.2 | Configure rsyslog" 15 | ansible.builtin.import_tasks: 16 | file: cis_5.1.2.x.yml 17 | when: 18 | - ubtu20cis_syslog_service == 'rsyslog' 19 | 20 | - name: "SECTION | 5.1.3 | Configure logfiles" 21 | ansible.builtin.import_tasks: 22 | file: cis_5.1.3.yml 23 | 24 | - name: "SECTION | 5.2.1.x | Configure auditd" 25 | ansible.builtin.import_tasks: 26 | file: cis_5.2.1.x.yml 27 | 28 | - name: "SECTION | 5.2.2.x | Configure auditd data retention" 29 | ansible.builtin.import_tasks: 30 | file: cis_5.2.2.x.yml 31 | 32 | - name: "SECTION | 5.2.3.x | Configure auditd rules" 33 | ansible.builtin.import_tasks: 34 | file: cis_5.2.3.x.yml 35 | 36 | - name: "SECTION | 5.2.4.x | Configure auditd file access" 37 | ansible.builtin.import_tasks: 38 | file: cis_5.2.4.x.yml 39 | -------------------------------------------------------------------------------- /tasks/section_6/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "SECTION | 6.1 | System File Permissions" 3 | ansible.builtin.import_tasks: 4 | file: cis_6.1.x.yml 5 | 6 | - name: "SECTION | 6.2 | User and Group Settings" 7 | ansible.builtin.import_tasks: 8 | file: cis_6.2.x.yml 9 | -------------------------------------------------------------------------------- /tasks/warning_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # This task is used to create variables used in giving a warning summary for manual tasks 4 | # that need attention 5 | # 6 | # The warn_control_list and warn_count vars start life in vars/main.yml but get updated 7 | # as the tasks that have a warning complete 8 | # 9 | # Those two variables are used in the tasks/main.yml to display a list of warnings 10 | # 11 | # warn_control_id is set within the task itself and has the control ID as the value 12 | # 13 | # warn_control_list is the main variable to be used and is a list made up of the warn_control_id’s 14 | # 15 | # warn_count the main variable for the number of warnings and each time a warn_control_id is added 16 | # the count increases by a value of 1 17 | - name: "{{ warn_control_id }} | AUDIT | Set fact for manual task warning." 18 | ansible.builtin.set_fact: 19 | warn_control_list: "{{ warn_control_list }} [{{ warn_control_id }}]" 20 | warn_count: "{{ warn_count | int + 1 }}" 21 | -------------------------------------------------------------------------------- /templates/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-lockdown/UBUNTU20-CIS/4d88c984b88eb1abbbc7a54cb09f0085f853bbe1/templates/.DS_Store -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_10_mount.rules.j2: -------------------------------------------------------------------------------- 1 | -a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=unset -k mounts 2 | {% if ansible_architecture == 'x86_64' -%} 3 | -a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=unset -k mounts 4 | {% endif %} 5 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_11_session.rules.j2: -------------------------------------------------------------------------------- 1 | -w /var/run/utmp -p wa -k session 2 | -w /var/log/wtmp -p wa -k session 3 | -w /var/log/btmp -p wa -k session 4 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_12_logins.rules.j2: -------------------------------------------------------------------------------- 1 | -w /var/run/faillock -p wa -k logins 2 | -w /var/log/lastlog -p wa -k logins 3 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_13_delete.rules.j2: -------------------------------------------------------------------------------- 1 | -a always,exit -F arch=b32 -S rename,unlink,unlinkat,renameat -F auid>=1000 -F auid!=unset -k delete 2 | {% if ansible_architecture == 'x86_64' -%} 3 | -a always,exit -F arch=b64 -S rename,unlink,unlinkat,renameat -F auid>=1000 -F auid!=unset -k delete 4 | {% endif %} 5 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_14_macpolicy.rules.j2: -------------------------------------------------------------------------------- 1 | -w /etc/apparmor/ -p wa -k MAC-policy 2 | -w /etc/apparmor.d/ -p wa -k MAC-policy 3 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_19_modules.rules.j2: -------------------------------------------------------------------------------- 1 | {% if ansible_architecture == 'x86_64' -%} 2 | -a always,exit -F arch=b64 -S init_module,finit_module,delete_module,create_module,query_module -F auid>=1000 -F auid!=unset -k kernel_modules 3 | {% endif %} 4 | -a always,exit -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -k kernel_modules 5 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_1_scope.rules.j2: -------------------------------------------------------------------------------- 1 | -w /etc/sudoers -p wa -k scope 2 | -w /etc/sudoers.d/ -p wa -k scope 3 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_20_finalize.rules.j2: -------------------------------------------------------------------------------- 1 | -e 2 2 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_2_user_emulation.rules.j2: -------------------------------------------------------------------------------- 1 | -a always,exit -F arch=b32 -C euid!=uid -F euid=0 -F auid>=1000 -F auid!=unset -S execve -k user_emulation 2 | {% if ansible_architecture == 'x86_64' -%} 3 | -a always,exit -F arch=b64 -C euid!=uid -F euid=0 -F auid>=1000 -F auid!=unset -S execve -k user_emulation 4 | {% endif %} 5 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_3_sudo_log.rules.j2: -------------------------------------------------------------------------------- 1 | -w {{ ubtu20cis_sudo_logfile }} -p wa -k sudo_log_file 2 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_4_timechange.rules.j2: -------------------------------------------------------------------------------- 1 | {% if ansible_architecture == 'x86_64' -%} 2 | -a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change 3 | {% endif %} 4 | -a always,exit -F arch=b32 -S adjtimex,settimeofday,clock_settime -k time-change 5 | -w /etc/localtime -p wa -k time-change 6 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_5_systemlocale.rules.j2: -------------------------------------------------------------------------------- 1 | {% if ansible_architecture == 'x86_64' -%} 2 | -a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale 3 | {% endif %} 4 | -a always,exit -F arch=b32 -S sethostname,setdomainname -k system-locale 5 | -w /etc/issue -p wa -k system-locale 6 | -w /etc/issue.net -p wa -k system-locale 7 | -w /etc/hosts -p wa -k system-locale 8 | -w /etc/networks -p wa -k system-locale 9 | -w /etc/network/ -p wa -k system-locale 10 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_6_privileged.rules.j2: -------------------------------------------------------------------------------- 1 | {% for proc in priv_procs.stdout_lines -%} 2 | -a always,exit -F path={{ proc }} -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_7_access.rules.j2: -------------------------------------------------------------------------------- 1 | {% if ansible_architecture == 'x86_64' -%} 2 | -a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access 3 | -a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access 4 | {% endif %} 5 | -a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access 6 | -a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access 7 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_8_identity.rules.j2: -------------------------------------------------------------------------------- 1 | -w /etc/group -p wa -k identity 2 | -w /etc/passwd -p wa -k identity 3 | -w /etc/gshadow -p wa -k identity 4 | -w /etc/shadow -p wa -k identity 5 | -w /etc/security/opasswd -p wa -k identity 6 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_9_permmod.rules.j2: -------------------------------------------------------------------------------- 1 | {% if ansible_architecture == 'x86_64' -%} 2 | -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -k perm_mod 3 | -a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=unset -k perm_mod 4 | -a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod 5 | {% endif %} 6 | -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -k perm_mod 7 | -a always,exit -F arch=b32 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=unset -k perm_mod 8 | -a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod 9 | -------------------------------------------------------------------------------- /templates/audit/ubtu20cis_5_2_3_x_perm_chng.rules.j2: -------------------------------------------------------------------------------- 1 | {% if ubtu20cis_rule_5_2_3_15 %} 2 | -a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng 3 | {% endif %} 4 | {% if ubtu20cis_rule_5_2_3_16 %} 5 | -a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng 6 | {% endif %} 7 | {% if ubtu20cis_rule_5_2_3_17 %} 8 | -a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng 9 | {% endif %} 10 | {% if ubtu20cis_rule_5_2_3_18 %} 11 | -a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -k usermod 12 | {% endif %} 13 | -------------------------------------------------------------------------------- /templates/chrony.conf.j2: -------------------------------------------------------------------------------- 1 | # Welcome to the chrony configuration file. See chrony.conf(5) for more 2 | # information about usuable directives. 3 | 4 | # This will use (up to): 5 | # - 4 sources from ntp.ubuntu.com which some are ipv6 enabled 6 | # - 2 sources from 2.ubuntu.pool.ntp.org which is ipv6 enabled as well 7 | # - 1 source from [01].ubuntu.pool.ntp.org each (ipv4 only atm) 8 | # This means by default, up to 6 dual-stack and up to 2 additional IPv4-only 9 | # sources will be used. 10 | # At the same time it retains some protection against one of the entries being 11 | # down (compare to just using one of the lines). See (LP: #1754358) for the 12 | # discussion. 13 | # 14 | # About using servers from the NTP Pool Project in general see (LP: #104525). 15 | # Approved by Ubuntu Technical Board on 2011-02-08. 16 | # See http://www.pool.ntp.org/join.html for more information. 17 | 18 | {% for server in ubtu20cis_time_synchronization_servers -%} 19 | server {{ server }} {{ ubtu20cis_chrony_server_options }} 20 | {% endfor %} 21 | 22 | # This directive specify the location of the file containing ID/key pairs for 23 | # NTP authentication. 24 | keyfile /etc/chrony/chrony.keys 25 | 26 | # Set runtime command key. Note that if you change the key (not the 27 | # password) to anything other than 1 you will need to edit 28 | # /etc/ppp/ip-up.d/chrony, /etc/ppp/ip-down.d/chrony, /etc/init.d/chrony 29 | # and /etc/cron.weekly/chrony as these scripts use it to get the password. 30 | 31 | #commandkey 1 32 | 33 | # This directive specify the file into which chronyd will store the rate 34 | # information. 35 | driftfile /var/lib/chrony/chrony.drift 36 | 37 | # Uncomment the following line to turn logging on. 38 | #log tracking measurements statistics 39 | 40 | # Log files location. 41 | logdir /var/log/chrony 42 | 43 | # Stop bad estimates upsetting machine clock. 44 | maxupdateskew 100.0 45 | 46 | # This directive enables kernel synchronisation (every 11 minutes) of the 47 | # real-time clock. Note that it can’t be used along with the 'rtcfile' directive. 48 | rtcsync 49 | 50 | # Dump measurements when daemon exits. 51 | dumponexit 52 | 53 | # Specify directory for dumping measurements. 54 | 55 | dumpdir /var/lib/chrony 56 | 57 | # Let computer be a server when it is unsynchronised. 58 | 59 | local stratum 10 60 | 61 | # Allow computers on the unrouted nets to use the server. 62 | 63 | #allow 10/8 64 | #allow 192.168/16 65 | #allow 172.16/12 66 | 67 | # This directive forces `chronyd' to send a message to syslog if it 68 | # makes a system clock adjustment larger than a threshold value in seconds. 69 | 70 | logchange 0.5 71 | 72 | # This directive defines an email address to which mail should be sent 73 | # if chronyd applies a correction exceeding a particular threshold to the 74 | # system clock. 75 | 76 | # mailonchange root@localhost 0.5 77 | 78 | # This directive tells chrony to regulate the real-time clock and tells it 79 | # Where to store related data. It may not work on some newer motherboards 80 | # that use the HPET real-time clock. It requires enhanced real-time 81 | # support in the kernel. I've commented it out because with certain 82 | # combinations of motherboard and kernel it is reported to cause lockups. 83 | 84 | # rtcfile /var/lib/chrony/chrony.rtc 85 | 86 | # If the last line of this file reads 'rtconutc' chrony will assume that 87 | # the CMOS clock is on UTC (GMT). If it reads '# rtconutc' or is absent 88 | # chrony will assume local time. The line (if any) was written by the 89 | # chrony postinst based on what it found in /etc/default/rcS. You may 90 | # change it if necessary. 91 | rtconutc 92 | 93 | user {{ ubtu20cis_chrony_user }} 94 | -------------------------------------------------------------------------------- /templates/etc/ansible/compliance_facts.j2: -------------------------------------------------------------------------------- 1 | # CIS Hardening Carried out 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | [lockdown_details] 6 | # Benchmark release 7 | Benchmark_release = CIS-{{ benchmark_version }} 8 | Benchmark_run_date = {{ '%Y-%m-%d - %H:%M:%S' | ansible.builtin.strftime }} 9 | # If options set (doesn't mean it ran all controls) 10 | level_1_hardening_enabled = {{ ubtu20cis_level_1 }} 11 | level_2_hardening_enabled = {{ ubtu20cis_level_2 }} 12 | 13 | {% if ansible_run_tags | length > 0 %} 14 | # If tags used to stipulate run level 15 | {% if 'level1-server' in ansible_run_tags %} 16 | Level_1_Server_tag_run = true 17 | {% endif %} 18 | {% if 'level2-server' in ansible_run_tags %} 19 | Level_2_Server_tag_run = true 20 | {% endif %} 21 | {% if 'level1-workstation' in ansible_run_tags %} 22 | Level_1_workstation_tag_run = true 23 | {% endif %} 24 | {% if 'level2-workstation' in ansible_run_tags %} 25 | Level_2_workstation_tag_run = true 26 | {% endif %} 27 | {% endif %} 28 | 29 | [lockdown_audit_details] 30 | {% if run_audit %} 31 | # Audit run 32 | audit_file_local_location = {{ audit_log_dir }} 33 | {% if not audit_only %} 34 | audit_summary = {{ post_audit_summary }} 35 | {% endif %} 36 | {% if fetch_audit_output %} 37 | audit_files_centralized_location = {{ audit_output_destination }} 38 | {% endif %} 39 | {% endif %} 40 | -------------------------------------------------------------------------------- /templates/etc/chrony/sources.d/time.sources.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company / Ansible Lockdown 4 | {% if ubtu20cis_chrony_timesource == 'pool' %} 5 | # pool chosen as source for time sync 6 | {% for pool in ubtu20cis_time_pool %} 7 | pool {{ pool.name }} {{ pool.options }} 8 | {% endfor %} 9 | {% elif ubtu20cis_chrony_timesource == 'servers' %} 10 | # Servers chosen as source for time sync 11 | {% for server in ubtu20cis_time_servers %} 12 | server {{ server.name }} {{ server.options }} 13 | {% endfor %} 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /templates/etc/issue.j2: -------------------------------------------------------------------------------- 1 | {{ ubtu20cis_warning_banner }} 2 | -------------------------------------------------------------------------------- /templates/etc/issue.net.j2: -------------------------------------------------------------------------------- 1 | {{ ubtu20cis_warning_banner }} 2 | -------------------------------------------------------------------------------- /templates/etc/motd.j2: -------------------------------------------------------------------------------- 1 | {{ ubtu20cis_warning_banner }} 2 | -------------------------------------------------------------------------------- /templates/etc/systemd/system/tmp.mount.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Temporary Directory /tmp 3 | ConditionPathIsSymbolicLink=!/tmp 4 | DefaultDependencies=no 5 | Conflicts=umount.target Before=local-fs.target umount.target 6 | After=swap.target 7 | 8 | [Mount] 9 | What=tmpfs 10 | Where=/tmp 11 | Type=tmpfs 12 | Options: {% if ubtu20cis_rule_1_1_2_2 %}nodev,{% endif %}{% if ubtu20cis_rule_1_1_2_3 %}noexec,{% endif %}{% if ubtu20cis_rule_1_1_2_4 %}nosuid{% endif %} 13 | -------------------------------------------------------------------------------- /templates/etc/systemd/timesyncd.conf.d/50-timesyncd.conf.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company / Ansible Lockdown 4 | 5 | [Time] 6 | 7 | NTP={% for pool in ubtu20cis_time_pool %}{{ pool.name }}{% endfor %} 8 | 9 | FallbackNTP={% for servers in ubtu20cis_time_servers %}{{ servers.name }} {% endfor %} 10 | -------------------------------------------------------------------------------- /templates/ntp.conf.j2: -------------------------------------------------------------------------------- 1 | # /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help 2 | 3 | driftfile /var/lib/ntp/ntp.drift 4 | 5 | # Leap seconds definition provided by tzdata 6 | leapfile /usr/share/zoneinfo/leap-seconds.list 7 | 8 | # Enable this if you want statistics to be logged. 9 | #statsdir /var/log/ntpstats/ 10 | 11 | statistics loopstats peerstats clockstats 12 | filegen loopstats file loopstats type day enable 13 | filegen peerstats file peerstats type day enable 14 | filegen clockstats file clockstats type day enable 15 | 16 | # Specify one or more NTP servers. 17 | 18 | # Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board 19 | # on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for 20 | # more information. 21 | {% for server in ubtu20cis_time_synchronization_servers -%} 22 | server {{ server }} {{ ubtu20cis_ntp_server_options }} 23 | {% endfor %} 24 | 25 | # Use Ubuntu's ntp server as a fallback. 26 | pool ntp.ubuntu.com 27 | 28 | # Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for 29 | # details. The web page 30 | # might also be helpful. 31 | # 32 | # Note that "restrict" applies to both servers and clients, so a configuration 33 | # that might be intended to block requests from certain clients could also end 34 | # up blocking replies from your own upstream servers. 35 | 36 | # By default, exchange time with everybody, but don't allow configuration. 37 | # The two lines below are required to meet CIS requirements 38 | restrict -4 default kod nomodify notrap nopeer noquery 39 | restrict -6 default kod nomodify notrap nopeer noquery 40 | 41 | # Local users may interrogate the ntp server more closely. 42 | restrict 127.0.0.1 43 | restrict ::1 44 | 45 | # Needed for adding pool entries 46 | restrict source notrap nomodify noquery 47 | 48 | # Clients from this (example!) subnet have unlimited access, but only if 49 | # cryptographically authenticated. 50 | #restrict 192.168.123.0 mask 255.255.255.0 notrust 51 | 52 | 53 | # If you want to provide time to your local subnet, change the next line. 54 | # (Again, the address is an example only.) 55 | #broadcast 192.168.123.255 56 | 57 | # If you want to listen to time broadcasts on your local subnet, de-comment the 58 | # next lines. Please do this only if you trust everybody on the network! 59 | #disable auth 60 | #broadcastclient 61 | 62 | #Changes recquired to use pps synchonisation as explained in documentation: 63 | #http://www.ntp.org/ntpfaq/NTP-s-config-adv.htm#AEN3918 64 | 65 | #server 127.127.8.1 mode 135 prefer # Meinberg GPS167 with PPS 66 | #fudge 127.127.8.1 time1 0.0042 # relative to PPS for my hardware 67 | 68 | #server 127.127.22.1 # ATOM(PPS) 69 | #fudge 127.127.22.1 flag3 1 # enable PPS API 70 | -------------------------------------------------------------------------------- /vars/audit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | #### Audit Configuration Settings #### 4 | 5 | # Timeout for those cmds that take longer to run where timeout set 6 | audit_cmd_timeout: 120000 7 | 8 | # if get_audit_binary_method == download change accordingly 9 | audit_bin_url: "https://github.com/goss-org/goss/releases/download/{{ audit_bin_version.release }}/goss-linux-" 10 | 11 | ### Goss Audit Benchmark file ### 12 | ## managed by the control audit_content 13 | # git 14 | audit_file_git: "https://github.com/ansible-lockdown/{{ benchmark }}-Audit.git" 15 | audit_git_version: "benchmark_{{ benchmark_version }}" 16 | 17 | ## Goss configuration information 18 | # Where the goss audit configuration will be stored - NOTE benchmark-audit is expected 19 | audit_conf_dir: "{{ audit_conf_dest | default('/opt') }}/{{ benchmark }}-Audit" 20 | 21 | # If changed these can affect other products 22 | pre_audit_outfile: "{{ audit_log_dir }}/{{ ansible_facts.hostname }}-{{ benchmark }}-{{ benchmark_version }}_pre_scan_{{ ansible_facts.date_time.epoch }}.{{ audit_format }}" 23 | post_audit_outfile: "{{ audit_log_dir }}/{{ ansible_facts.hostname }}-{{ benchmark }}-{{ benchmark_version }}_post_scan_{{ ansible_facts.date_time.epoch }}.{{ audit_format }}" 24 | 25 | ## The following should not need changing 26 | 27 | ### Audit binary settings ### 28 | audit_bin_version: 29 | release: v0.4.4 30 | AMD64_checksum: 'sha256:1c4f54b22fde9d4d5687939abc2606b0660a5d14a98afcd09b04b793d69acdc5' 31 | audit_bin_path: /usr/local/bin/ 32 | audit_bin: "{{ audit_bin_path }}goss" 33 | audit_format: json 34 | 35 | audit_vars_path: "{{ audit_conf_dir }}/vars/{{ ansible_facts.hostname }}.yml" 36 | audit_results: | 37 | The {% if not audit_only %}pre remediation{% endif %} audit results are: {{ pre_audit_summary }} 38 | {% if not audit_only %}The post remediation audit results are: {{ post_audit_summary }}{% endif %} 39 | 40 | Full breakdown can be found in {{ audit_log_dir }} 41 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for UBUNTU20-CIS 3 | 4 | min_ansible_version: 2.10.1 5 | 6 | # default setting, this should not be changed 7 | # and is overridden if a task that changed sets the value if required. 8 | change_requires_reboot: false 9 | 10 | # Used to control warning summary 11 | warn_control_list: "" 12 | warn_count: 0 13 | --------------------------------------------------------------------------------