|
|
|
@ -166,54 +166,7 @@ metadata: |
|
|
|
name: stackrox-init-hook |
|
|
|
namespace: stackrox |
|
|
|
data: |
|
|
|
configure-acs.sh: | |
|
|
|
#!/bin/bash |
|
|
|
|
|
|
|
set -Eeuo pipefail |
|
|
|
|
|
|
|
mkdir -p /tmp/bin |
|
|
|
curl -sfLo /tmp/bin/roxctl https://mirror.openshift.com/pub/rhacs/assets/4.0.0/bin/Linux/roxctl |
|
|
|
chmod 755 /tmp/bin/roxctl |
|
|
|
curl -sLo /tmp/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 |
|
|
|
chmod 755 /tmp/bin/jq |
|
|
|
export PATH="/tmp/bin:$PATH" |
|
|
|
|
|
|
|
echo "========================================================================" |
|
|
|
echo " Connecting to Red Hat ACS" |
|
|
|
echo "========================================================================" |
|
|
|
echo |
|
|
|
|
|
|
|
export ROX_CENTRAL_ADDRESS="$(oc get route central -n stackrox -o go-template='{{ "{{" }}.spec.host}}'):443" |
|
|
|
export ROX_CENTRAL_HOSTNAME="$ROX_CENTRAL_ADDRESS" |
|
|
|
while ! curl -sfko /dev/null "https://$ROX_CENTRAL_ADDRESS/"; do |
|
|
|
echo "Red Hat ACS not ready..." |
|
|
|
sleep 5 |
|
|
|
done |
|
|
|
|
|
|
|
echo "========================================================================" |
|
|
|
echo " Retrieving an API Token for Red Hat ACS" |
|
|
|
echo "========================================================================" |
|
|
|
echo |
|
|
|
if ! oc get secret stackrox-api-token -n stackrox &>/dev/null; then |
|
|
|
POLICY_JSON='{ "name": "init-token", "role":"Admin"}' |
|
|
|
APIURL="https://$ROX_CENTRAL_ADDRESS/v1/apitokens/generate" |
|
|
|
export ROX_API_TOKEN=$(curl -s -k -u admin:$ROX_ADMIN_PASSWORD -H 'Content-Type: application/json' -X POST -d "$POLICY_JSON" "$APIURL" | jq -r '.token') |
|
|
|
oc create secret generic stackrox-api-token -n stackrox --from-literal=token="$ROX_API_TOKEN" |
|
|
|
else |
|
|
|
export ROX_API_TOKEN="$(oc get secret stackrox-api-token -n stackrox -o go-template --template='{{ "{{" }}.data.token|base64decode}}')" |
|
|
|
fi |
|
|
|
|
|
|
|
echo "========================================================================" |
|
|
|
echo " Generating the Cluster Init Bundle" |
|
|
|
echo "========================================================================" |
|
|
|
echo |
|
|
|
|
|
|
|
if ! oc get secret admission-control-tls -n stackrox &>/dev/null; then |
|
|
|
roxctl -e "$ROX_CENTRAL_ADDRESS" central init-bundles generate local-cluster --output-secrets /tmp/cluster_init_bundle.yaml |
|
|
|
oc apply -f /tmp/cluster_init_bundle.yaml -n stackrox |
|
|
|
fi |
|
|
|
|
|
|
|
exit 0 |
|
|
|
{{ (.Files.Glob "files/stackrox-init-hook/*").AsConfig | indent 2 }} |
|
|
|
--- |
|
|
|
apiVersion: v1 |
|
|
|
kind: ConfigMap |
|
|
|
@ -223,202 +176,7 @@ metadata: |
|
|
|
name: stackrox-configure-hook |
|
|
|
namespace: stackrox |
|
|
|
data: |
|
|
|
entrypoint.sh: | |
|
|
|
#!/bin/bash |
|
|
|
|
|
|
|
set -Eeuo pipefail |
|
|
|
|
|
|
|
ansible-galaxy collection install community.general |
|
|
|
ansible-playbook configure.yaml |
|
|
|
|
|
|
|
exit 0 |
|
|
|
configure.yaml: | |
|
|
|
- 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: fruits-dev |
|
|
|
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: 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: |
|
|
|
namespace: dev |
|
|
|
severity: CRITICAL_SEVERITY |
|
|
|
|
|
|
|
- set_fact: |
|
|
|
policy_id: '{{ create_policy_response.json.id }}' |
|
|
|
when: policy_id is not defined |
|
|
|
|
|
|
|
- debug: |
|
|
|
var: policy_id |
|
|
|
{{ (.Files.Glob "files/stackrox-configure-hook/*").AsConfig | indent 2 }} |
|
|
|
--- |
|
|
|
apiVersion: batch/v1 |
|
|
|
kind: Job |
|
|
|
|