You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
9.5 KiB
313 lines
9.5 KiB
- 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
|
|
|