- name: Configure RHACS hosts: localhost gather_facts: no vars: ansible_connection: local acs_api: https://{{ central_hostname }}/v1 validate_certs: no tasks: - name: Get Stackrox central's Route kubernetes.core.k8s_info: api_version: route.openshift.io/v1 kind: Route name: central namespace: stackrox register: central_route failed_when: central_route.resources|length == 0 until: central_route is succeeded retries: 60 delay: 5 - set_fact: central_hostname: '{{ central_route.resources[0].spec.host }}:443' - name: Get Stackrox central's admin password kubernetes.core.k8s_info: api_version: v1 kind: Secret name: central-admin namespace: stackrox register: admin_secret failed_when: admin_secret.resources|length == 0 until: admin_secret is succeeded retries: 60 delay: 5 - set_fact: central_admin_password: '{{ admin_secret.resources[0].data.password | b64decode }}' - name: Get Cosign public key kubernetes.core.k8s_info: api_version: v1 kind: Secret name: code-signature namespace: stackrox register: cosign_secret failed_when: cosign_secret.resources|length == 0 until: cosign_secret is succeeded retries: 60 delay: 5 - set_fact: cosign_public_key: '{{ cosign_secret.resources[0].data["cosign.pub"] | b64decode }}' - name: Check if jmespath is available locally debug: msg={{ dummy|json_query('@') }} register: check_jmespath ignore_errors: yes vars: dummy: Hello World - name: Ensure JMESPath is installed assert: that: - 'check_jmespath is success' msg: > The JMESPath library is required by this playbook. Please install the JMESPath library with 'pip install jmespath'. - name: Wait for the Central to be ready uri: url: 'https://{{ central_hostname }}' validate_certs: '{{ validate_certs }}' register: healthcheck changed_when: false until: healthcheck is succeeded retries: 60 delay: 5 - name: Get K8s secret kubernetes.core.k8s_info: api_version: v1 kind: Secret name: stackrox-cicd-token namespace: stackrox register: cicd_token_secret - name: Create the CI/CD API Token uri: url: '{{ acs_api }}/apitokens/generate' method: POST status_code: "200" validate_certs: '{{ validate_certs }}' url_username: admin url_password: '{{ central_admin_password }}' body: '{{ token_creation }}' body_format: json force_basic_auth: yes register: create_apitoken_response changed_when: create_apitoken_response.status == 200 when: cicd_token_secret.resources|length == 0 vars: token_creation: name: tekton-pipelines role: Continuous Integration - set_fact: apitoken_value: '{{ create_apitoken_response.json.token }}' when: cicd_token_secret.resources|length == 0 - name: Create the K8s Secret kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Secret metadata: name: stackrox-cicd-token namespace: stackrox stringData: token: '{{ apitoken_value }}' endpoint: '{{ central_hostname }}' when: apitoken_value is defined - name: Get secrets in the stackrox namespace kubernetes.core.k8s_info: api_version: v1 kind: Secret namespace: stackrox register: stackrox_secrets failed_when: stackrox_secrets.resources|length == 0 - set_fact: registry_reader_token: '{{ stackrox_secrets.resources | json_query(query) | first | b64decode }}' vars: query: > [?metadata.annotations."kubernetes.io/service-account.name" == `stackrox-registry-reader` && type == `kubernetes.io/service-account-token`].data.token - name: Find image registry integrations uri: url: '{{ acs_api }}/imageintegrations' validate_certs: '{{ validate_certs }}' url_username: admin url_password: '{{ central_admin_password }}' force_basic_auth: yes register: find_image_integrations_response changed_when: false - set_fact: image_integration_id: '{{ (find_image_integrations_response.json | json_query(query) | first).id }}' when: find_image_integrations_response.json | json_query(query) | count > 0 vars: query: integrations[?type == `docker` && docker.endpoint == `image-registry.openshift-image-registry.svc:5000`] - name: Create the image registry integration uri: url: '{{ acs_api }}/imageintegrations' method: POST status_code: "200" validate_certs: '{{ validate_certs }}' url_username: admin url_password: '{{ central_admin_password }}' body: '{{ integration }}' body_format: json force_basic_auth: yes register: create_image_integration_response changed_when: create_image_integration_response.status == 200 when: image_integration_id is not defined vars: integration: name: OpenShift Internal Registry autogenerated: false categories: - REGISTRY clusterId: "" docker: endpoint: image-registry.openshift-image-registry.svc:5000 insecure: true username: stackrox-registry-reader password: '{{ registry_reader_token }}' type: docker - set_fact: image_integration_id: '{{ create_image_integration_response.json.id }}' when: image_integration_id is not defined - debug: var: image_integration_id - name: Find signature integrations uri: url: '{{ acs_api }}/signatureintegrations' validate_certs: '{{ validate_certs }}' url_username: admin url_password: '{{ central_admin_password }}' force_basic_auth: yes register: find_signature_integrations_response changed_when: false - set_fact: signature_integration_id: '{{ (find_signature_integrations_response.json | json_query(query) | first).id }}' when: find_signature_integrations_response.json | json_query(query) | count > 0 vars: query: integrations[?name == `Sigstore`] - name: Create the Cosign signature integration uri: url: '{{ acs_api }}/signatureintegrations' method: POST status_code: "200" validate_certs: '{{ validate_certs }}' url_username: admin url_password: '{{ central_admin_password }}' body: '{{ integration }}' body_format: json force_basic_auth: yes register: create_signature_integration_response changed_when: create_signature_integration_response.status == 200 when: signature_integration_id is not defined vars: integration: name: Sigstore cosign: publicKeys: - name: cosign.pub publicKeyPemEnc: '{{ cosign_public_key }}' - set_fact: signature_integration_id: '{{ create_signature_integration_response.json.id }}' when: signature_integration_id is not defined - debug: var: signature_integration_id - name: Find policies uri: url: '{{ acs_api }}/policies?query=Policy%3AImage%20is%20not%20signed' validate_certs: '{{ validate_certs }}' url_username: admin url_password: '{{ central_admin_password }}' force_basic_auth: yes register: find_policies_response changed_when: false - set_fact: policy_id: '{{ (find_policies_response.json.policies | first).id }}' when: find_policies_response.json.policies | count > 0 - name: Create the policy uri: url: '{{ acs_api }}/policies' method: POST status_code: "200" validate_certs: '{{ validate_certs }}' url_username: admin url_password: '{{ central_admin_password }}' body: '{{ policy }}' body_format: json force_basic_auth: yes register: create_policy_response changed_when: create_policy_response.status == 200 when: policy_id is not defined vars: policy: SORTEnforcement: no SORTLifecycleStage: '' SORTName: '' categories: - Security Best Practices criteriaLocked: no description: The container image has not been digitally signed. disabled: no enforcementActions: - SCALE_TO_ZERO_ENFORCEMENT - UNSATISFIABLE_NODE_CONSTRAINT_ENFORCEMENT eventSource: NOT_APPLICABLE exclusions: [] isDefault: no lifecycleStages: - DEPLOY mitreAttackVectors: [] mitreVectorsLocked: no name: Image is not signed notifiers: [] policySections: - policyGroups: - booleanOperator: OR fieldName: Image Signature Verified By negate: no values: - value: "{{ signature_integration_id }}" sectionName: Policy Section 1 policyVersion: '1.1' rationale: The container image MUST be digitally signed in order to prevent tampering. remediation: Use cosign to sign this image. See https://docs.sigstore.dev/cosign/signing_with_containers/ scope: - cluster: label: key: app value: eshop-web namespace: eshop-prod - cluster: label: key: app value: eshop-api namespace: eshop-prod severity: CRITICAL_SEVERITY - set_fact: policy_id: '{{ create_policy_response.json.id }}' when: policy_id is not defined - debug: var: policy_id