diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 0000000..2975c5a --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,29 @@ +--- +name: Gitleaks + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + gitleaks: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run Gitleaks + uses: gitleaks/gitleaks-action@v2 + with: + args: --config .gitleaks.toml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/slack-pr-notifications.yml b/.github/workflows/slack-pr-notifications.yml new file mode 100644 index 0000000..d3498d3 --- /dev/null +++ b/.github/workflows/slack-pr-notifications.yml @@ -0,0 +1,69 @@ +name: Slack PR Notifications + +on: + pull_request: + types: [opened, closed, reopened] + branches: ["main"] + pull_request_review: + types: [submitted] + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Set notification details + id: details + env: + EVENT_NAME: ${{ github.event_name }} + EVENT_ACTION: ${{ github.event.action }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_MERGED: ${{ github.event.pull_request.merged }} + PR_USER: ${{ github.event.pull_request.user.login }} + REVIEW_STATE: ${{ github.event.review.state }} + REVIEW_USER: ${{ github.event.review.user.login }} + run: | + if [[ "$EVENT_NAME" == "pull_request_review" ]]; then + TITLE="PR Review: ${REVIEW_STATE} - ${PR_TITLE}" + COLOR=$([[ "$REVIEW_STATE" == "approved" ]] && echo "good" || echo "warning") + BODY="${REVIEW_USER} ${REVIEW_STATE} the PR" + else + TITLE="PR ${EVENT_ACTION^}: ${PR_TITLE}" + if [[ "$EVENT_ACTION" == "closed" && "$PR_MERGED" == "true" ]]; then + TITLE="PR Merged: ${PR_TITLE}" + COLOR="good" + elif [[ "$EVENT_ACTION" == "opened" ]]; then + COLOR="#1a73e8" + elif [[ "$EVENT_ACTION" == "reopened" ]]; then + COLOR="warning" + else + COLOR="danger" + fi + BODY="${PR_USER} ${EVENT_ACTION} the PR" + fi + + echo "title=${TITLE}" >> $GITHUB_OUTPUT + echo "color=${COLOR}" >> $GITHUB_OUTPUT + echo "body=${BODY}" >> $GITHUB_OUTPUT + + - name: Send Slack notification + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "attachments": [ + { + "color": "${{ steps.details.outputs.color }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*${{ steps.details.outputs.title }}*\n${{ steps.details.outputs.body }}\n*Repo:* `${{ github.repository }}`\n*Branch:* `${{ github.event.pull_request.head.ref }}` -> `${{ github.event.pull_request.base.ref }}`\n<${{ github.event.pull_request.html_url }}|View Pull Request>" + } + } + ] + } + ] + } diff --git a/.gitignore b/.gitignore index 75ab102..f49ecaf 100644 --- a/.gitignore +++ b/.gitignore @@ -186,3 +186,7 @@ cython_debug/ *.sarif **tar.gz + +# Secrets and certificates +*.pem +*.key diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..972b0b5 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,70 @@ +# Gitleaks configuration for openshift_virtualization_migration +# https://github.com/gitleaks/gitleaks + +title = "OpenShift Virtualization Migration Gitleaks Configuration" + +[extend] +useDefault = true + +# Allowlist paths and patterns that contain placeholder credentials +# (e.g., "changeme", example domains, template variables) +[allowlist] +description = "Global allowlist for placeholder values and template files" +paths = [ + '''\.gitleaks\.toml$''', + '''README\.md$''', +] +regexTarget = "line" +regexes = [ + # Placeholder values used in inventory and defaults + '''changeme''', + # Jinja2 template variables + '''\{\{.*\}\}''', + # Ansible Vault references + '''!vault''', + # Example/documentation values + '''example\.com''', + '''EXAMPLE''', + # YAML comments containing credential variable names + '''^\s*#.*''', + # HTML bold tags documenting variable names (docsible-generated) + '''.*''', + # Multi-line YAML block scalars where value is a Jinja2 template on the next line + '''[>|][+-]?\s*$''', +] + +# Custom rules for Ansible-specific credential patterns +[[rules]] +id = "ansible-vault-password-file" +description = "Ansible vault password file" +regex = '''vault[_-]?pass(word)?[_-]?file\s*[:=]\s*['"]?([^\s'"]+)''' +keywords = ["vault"] +[rules.allowlist] +regexes = ['''changeme''', '''\{\{.*\}\}'''] + +[[rules]] +id = "openshift-api-key" +description = "OpenShift API key or token" +regex = '''(?i)(openshift[_-]?(?:api[_-]?key|token|password))\s*[:=]\s*['"]?([^\s'"#}{]+)''' +keywords = ["openshift"] +[rules.allowlist] +regexes = ['''changeme''', '''\{\{.*\}\}''', '''example''', '''.*''', '''[>|][+-]?\s*$'''] +paths = ['''defaults/main\.yml$''', '''inventory\.yml$''', '''README\.md$''', '''tasks/main\.yml$'''] + +[[rules]] +id = "automation-hub-token" +description = "Automation Hub token" +regex = '''(?i)(automation[_-]?hub[_-]?(?:token|password))\s*[:=]\s*['"]?([^\s'"#}{]+)''' +keywords = ["automation_hub", "automation-hub"] +[rules.allowlist] +regexes = ['''changeme''', '''\{\{.*\}\}'''] +paths = ['''defaults/main\.yml$''', '''inventory\.yml$'''] + +[[rules]] +id = "container-registry-password" +description = "Container registry password" +regex = '''(?i)(container[_-]?password|registry[_-]?password)\s*[:=]\s*['"]?([^\s'"#}{]+)''' +keywords = ["container_password", "registry_password"] +[rules.allowlist] +regexes = ['''changeme''', '''\{\{.*\}\}''', '''.*'''] +paths = ['''defaults/main\.yml$''', '''inventory\.yml$''', '''README\.md$'''] diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d18df5..9d52b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # CHANGELOG +## v1.22.1 (2026-05-18) + +### Security + +- Remove hardcoded `secure_logging: false` override in aap_seed role; role default of `true` now applies +- Add `no_log: true` to all `ansible.builtin.uri` tasks that pass Authorization headers or credentials +- Document cluster-admin risk in ClusterRoleBinding for migration-factory-aap service account +- Fix inverted secure_logging comment in inventory.yml +- Add `.env`, `*.pem`, `*.key` patterns to .gitignore + ## v1.22.0 (2026-04-02) ### Bug Fixes diff --git a/inventory.yml b/inventory.yml index 677b9bf..9f99a8f 100644 --- a/inventory.yml +++ b/inventory.yml @@ -16,7 +16,7 @@ all: aap_validate_certs: false controller_validate_certs: false - # If secure_logging is set to 'true', Secrets may be displayed in logs. + # If secure_logging is set to 'true', secrets are hidden from logs. # secure_logging: false ## Operators to deploy on the OpenShift Hub Cluster diff --git a/roles/aap_deploy/tasks/install.yml b/roles/aap_deploy/tasks/install.yml index 02b9fed..357286d 100644 --- a/roles/aap_deploy/tasks/install.yml +++ b/roles/aap_deploy/tasks/install.yml @@ -1,5 +1,6 @@ --- - name: install | Validate OpenShift bearer token + no_log: true ansible.builtin.uri: url: "{{ aap_deploy_openshift_host | default(lookup('ansible.builtin.env', 'K8S_AUTH_HOST')) }}" method: GET diff --git a/roles/aap_seed/tasks/main.yml b/roles/aap_seed/tasks/main.yml index a9695bb..4645ed4 100644 --- a/roles/aap_seed/tasks/main.yml +++ b/roles/aap_seed/tasks/main.yml @@ -56,6 +56,7 @@ delay: 5 register: aap_seed_api_status until: aap_seed_api_status.status == 200 + no_log: true ansible.builtin.uri: url: https://{{ aap_seed_controller_hostname }}/api{{ '/controller' if aap_version is not defined or aap_version is defined and aap_version is version('2.5', '>=') }}/v2/config/ # noqa: yaml[line-length] method: GET @@ -72,6 +73,7 @@ delay: 5 register: aap_seed_api_status until: aap_seed_api_status.status == 200 + no_log: true ansible.builtin.uri: url: https://{{ aap_seed_controller_hostname }}/api{{ '/controller' if aap_version is not defined or aap_version is defined and aap_version is version('2.5', '>=') }}/v2/config/ # noqa: yaml[line-length] method: GET @@ -94,9 +96,7 @@ - name: Set variables for {{ aap_seed_cac_collection }} ansible.builtin.set_fact: - controller_configuration_secure_logging: false # noqa: var-naming[no-role-prefix] controller_configuration_async_delay: 5 # noqa: var-naming[no-role-prefix] - aap_configuration_secure_logging: false # noqa: var-naming[no-role-prefix] aap_configuration_async_delay: 5 # noqa: var-naming[no-role-prefix] - name: Call dispatch role diff --git a/roles/bootstrap/tasks/aap_subscription.yml b/roles/bootstrap/tasks/aap_subscription.yml index d2ae563..01c7498 100644 --- a/roles/bootstrap/tasks/aap_subscription.yml +++ b/roles/bootstrap/tasks/aap_subscription.yml @@ -59,6 +59,7 @@ register: __bootstrap_aap_license_manifest_content - name: aap_subscription | Apply license to AAP + no_log: true ansible.builtin.uri: method: POST status_code: 200 diff --git a/roles/create_mf_aap_token/files/crb_migration_factory_aap_cluster_admin.yaml b/roles/create_mf_aap_token/files/crb_migration_factory_aap_cluster_admin.yaml index fd7512d..651936f 100644 --- a/roles/create_mf_aap_token/files/crb_migration_factory_aap_cluster_admin.yaml +++ b/roles/create_mf_aap_token/files/crb_migration_factory_aap_cluster_admin.yaml @@ -1,4 +1,5 @@ --- +# WARNING: cluster-admin grants unrestricted cluster access. Replace with a scoped ClusterRole for production use. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/roles/mtv_management/tasks/mtv_query_inventory.yml b/roles/mtv_management/tasks/mtv_query_inventory.yml index 27a6f15..5a3e31b 100644 --- a/roles/mtv_management/tasks/mtv_query_inventory.yml +++ b/roles/mtv_management/tasks/mtv_query_inventory.yml @@ -81,6 +81,7 @@ headers: Authorization: Bearer {{ openshift_api_key }} register: _mtv_management_mtv_inventory_query_result + no_log: true - name: mtv_query_inventory | Set Result Fact ansible.builtin.set_fact: diff --git a/roles/vm_hot_plug/tasks/_storage.yml b/roles/vm_hot_plug/tasks/_storage.yml index 8060e3d..d9846b5 100644 --- a/roles/vm_hot_plug/tasks/_storage.yml +++ b/roles/vm_hot_plug/tasks/_storage.yml @@ -19,6 +19,7 @@ default([]) | selectattr('name', 'equalto', vm_hot_plug_storage_instance.name) | list | length == 0 ) + no_log: true ansible.builtin.uri: url: "{{ vm_hot_plug_openshift_host }}/apis/subresources.{{ vm_hot_plug_kubevirt_api_version }}\ diff --git a/roles/vm_lifecycle/tasks/_perform_operation.yml b/roles/vm_lifecycle/tasks/_perform_operation.yml index cedb815..2926258 100644 --- a/roles/vm_lifecycle/tasks/_perform_operation.yml +++ b/roles/vm_lifecycle/tasks/_perform_operation.yml @@ -1,6 +1,7 @@ --- - name: _perform_operation | Perform VM Operation + no_log: true ansible.builtin.uri: url: "{{ vm_lifecycle_openshift_host }}/apis/subresources.{{ vm_lifecycle_kubevirt_api_version }}/namespaces/{{ vm_operations_vm.vm.metadata.namespace }}/virtualmachines/{{ vm_operations_vm.vm.metadata.name }}/{{ vm_lifecycle_valid_vm_operations[vm_operations_vm['operation']].endpoint }}" # noqa: yaml[line-length] validate_certs: "{{ vm_lifecycle_openshift_verify_ssl }}"