Base work for exporting encrypted logs
Our production jobs currently only put their logging locally on the bastion host. This means that to help maintain a production system, you effectively need full access to the bastion host to debug any misbehaviour. We've long discussed publishing these Ansible runs as public logs, or via a reporting system (ARA, etc.) but, despite our best efforts at no_log and similar, we are not 100% sure that secret values may not leak. This is the infrastructure for an in-between solution, where we publish the production run logs encrypted to specific GPG public keys. Here we are capturing and encrypting the logs of the system-config-run-* jobs, and providing a small download script to automatically grab and unencrypt the log files. Obviously this is just to exercise the encryption/log-download path for these jobs, as the logs are public. Once this has landed, I will propose similar for the production jobs (because these are post-pipeline this takes a bit more fiddling and doens't run in CI). The variables will be setup in such a way that if someone wishes to help maintain a production system, they can add their public-key and then add themselves to the particular infra-prod-* job they wish to view the logs for. It is planned that the extant operators will be in the default list; however this is still useful over the status quo -- instead of having to search through the log history on the bastion host when debugging a failed run, they can simply view the logs from the failing build in Zuul directly. Depends-On: https://review.opendev.org/c/zuul/zuul-jobs/+/828818/ Change-Id: I5b9f9dd53eb896bb542652e8175c570877842584
This commit is contained in:
parent
1fb51d22c2
commit
ccf00b7673
@ -32,3 +32,10 @@
|
|||||||
- name: Install rackspace DNS backup tool
|
- name: Install rackspace DNS backup tool
|
||||||
include_role:
|
include_role:
|
||||||
name: rax-dns-backup
|
name: rax-dns-backup
|
||||||
|
|
||||||
|
- name: Make ansible log directory
|
||||||
|
file:
|
||||||
|
path: '/var/log/ansible'
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
mode: 0755
|
||||||
|
85
playbooks/zuul/roles/encrypt-logs/defaults/main.yaml
Normal file
85
playbooks/zuul/roles/encrypt-logs/defaults/main.yaml
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Anyone who wants to be able to read encrypted published logs should
|
||||||
|
# have an entry in this variable in the format
|
||||||
|
#
|
||||||
|
# - name: <freeform string name>
|
||||||
|
# key_id: <key-id of GPG key>
|
||||||
|
# gpg_asc: <ASCII-armored PGP public key block>
|
||||||
|
#
|
||||||
|
encrypt_logs_keys:
|
||||||
|
- name: 'ianw'
|
||||||
|
key_id: '0x9615aec8'
|
||||||
|
gpg_asc: |
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Version: GnuPG v1
|
||||||
|
|
||||||
|
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
|
||||||
|
YyJrcbCb0lGKJnpdWIXnDldS91E0Lw==
|
||||||
|
=jbiD
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
# This is the default list of keys from ``encrypt_log_keys`` that wish
|
||||||
|
# to always be a recipient of encrypted logs. Others can add
|
||||||
|
# themselves to particular prod jobs of interest individually.
|
||||||
|
encrypt_logs_recipients:
|
||||||
|
- ianw
|
||||||
|
|
27
playbooks/zuul/roles/encrypt-logs/tasks/main.yaml
Normal file
27
playbooks/zuul/roles/encrypt-logs/tasks/main.yaml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
- name: Encrypt file
|
||||||
|
include_role:
|
||||||
|
name: encrypt-file
|
||||||
|
vars:
|
||||||
|
encrypt_file: '{{ encrypt_logs_files }}'
|
||||||
|
encrypt_file_keys: '{{ encrypt_logs_keys }}'
|
||||||
|
encrypt_file_recipients: '{{ encrypt_logs_recipients + encrypt_logs_job_recipients|default([]) }}'
|
||||||
|
|
||||||
|
- name: Write download script
|
||||||
|
template:
|
||||||
|
src: download-logs.sh.j2
|
||||||
|
dest: '{{ encrypt_logs_download_script_path }}/download-logs.sh'
|
||||||
|
mode: 0755
|
||||||
|
vars:
|
||||||
|
encrypt_logs_download_api: 'https://zuul.opendev.org/api/tenant/{{ zuul.tenant }}'
|
||||||
|
|
||||||
|
- name: Return artifact
|
||||||
|
zuul_return:
|
||||||
|
data:
|
||||||
|
zuul:
|
||||||
|
artifacts:
|
||||||
|
# This is parsed by the log download script above, so any
|
||||||
|
# changes to format must be accounted for there too.
|
||||||
|
- name: Encrypted logs
|
||||||
|
url: '{{ encrypt_logs_artifact_path }}'
|
||||||
|
metadata:
|
||||||
|
logfiles: "{{ encrypt_logs_files | map('basename') | map('regex_replace', '^(.*)$', '\\1.gpg') | list }}"
|
@ -0,0 +1,89 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ZUUL_API=${ZUUL_API:-"{{ encrypt_logs_download_api }}"}
|
||||||
|
ZUUL_BUILD_UUID=${ZUUL_BUILD_UUID:-"{{ zuul.build }}"}
|
||||||
|
{% raw %}
|
||||||
|
ZUUL_API_URL=${ZUUL_API}/build/${ZUUL_BUILD_UUID}
|
||||||
|
|
||||||
|
(( ${BASH_VERSION%%.*} >= 4 )) || { echo >&2 "bash >=4 required to download."; exit 1; }
|
||||||
|
command -v python3 >/dev/null 2>&1 || { echo >&2 "Python3 is required to download."; exit 1; }
|
||||||
|
command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required to download."; exit 1; }
|
||||||
|
|
||||||
|
function log {
|
||||||
|
echo "$(date -Iseconds) | $@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_urls {
|
||||||
|
/usr/bin/env python3 - <<EOF
|
||||||
|
import gzip
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
base_url = urllib.request.urlopen("${ZUUL_API_URL}").read()
|
||||||
|
base_json = json.loads(base_url)
|
||||||
|
for a in base_json['artifacts']:
|
||||||
|
if a['name'] == 'Encrypted logs':
|
||||||
|
url = a['url']
|
||||||
|
logfiles = (url + '/' + f for f in a['metadata']['logfiles'])
|
||||||
|
for l in logfiles:
|
||||||
|
print(l)
|
||||||
|
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.code == 404:
|
||||||
|
print(
|
||||||
|
"Could not find build UUID in Zuul API. This can happen with "
|
||||||
|
"buildsets still running, or aborted ones. Try again after the "
|
||||||
|
"buildset is reported back to Zuul.", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(e, file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_file {
|
||||||
|
local exit_code=0
|
||||||
|
local xtra_args="--compressed"
|
||||||
|
|
||||||
|
curl -s ${xtra_args} -o $(basename "${file}") "${file}" || exit_code=$?
|
||||||
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
|
log "Failed to download ${base_url}${file}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
log "Querying ${ZUUL_API_URL} for manifest"
|
||||||
|
_files="$(get_urls)"
|
||||||
|
readarray -t files <<< "${_files}"
|
||||||
|
|
||||||
|
len="${#files[@]}"
|
||||||
|
if [[ -z "${DOWNLOAD_DIR}" ]]; then
|
||||||
|
DOWNLOAD_DIR=$(mktemp -d --tmpdir zuul-logs.XXXXXX)
|
||||||
|
fi
|
||||||
|
log "Saving logs to ${DOWNLOAD_DIR}"
|
||||||
|
|
||||||
|
pushd "${DOWNLOAD_DIR}" > /dev/null
|
||||||
|
|
||||||
|
log "Getting logs from ${ZUUL_BUILD_ID}"
|
||||||
|
for (( i=1; i<$len; i++ )); do
|
||||||
|
file="${files[i]}"
|
||||||
|
printf -v _out " %-80s [ %04d/%04d ]" "${file}" "${i}" $(( len -1 ))
|
||||||
|
log "$_out"
|
||||||
|
save_file $file
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in ${DOWNLOAD_DIR}/*.gpg; do
|
||||||
|
log "Decrypting $(basename $f)"
|
||||||
|
gpg --output ${f/.gpg/} --decrypt ${f}
|
||||||
|
rm ${f}
|
||||||
|
done
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
log "Download to ${DOWNLOAD_DIR} complete!"
|
||||||
|
{% endraw %}
|
@ -92,18 +92,35 @@
|
|||||||
- name: Display group membership
|
- name: Display group membership
|
||||||
command: ansible localhost -m debug -a 'var=groups'
|
command: ansible localhost -m debug -a 'var=groups'
|
||||||
- name: Run base.yaml
|
- name: Run base.yaml
|
||||||
command: ansible-playbook -f 50 -v /home/zuul/src/opendev.org/opendev/system-config/playbooks/base.yaml
|
shell: 'ansible-playbook -f 50 -v /home/zuul/src/opendev.org/opendev/system-config/playbooks/base.yaml 2>&1 | tee /var/log/ansible/base.yaml.log'
|
||||||
- name: Run bridge service playbook
|
- name: Run bridge service playbook
|
||||||
command: ansible-playbook -v /home/zuul/src/opendev.org/opendev/system-config/playbooks/service-bridge.yaml
|
shell: 'ansible-playbook -v /home/zuul/src/opendev.org/opendev/system-config/playbooks/service-bridge.yaml 2>&1 | tee /var/log/ansible/service-bridge.yaml.log'
|
||||||
- name: Run dstat logger playbook
|
- name: Run dstat logger playbook
|
||||||
command: ansible-playbook -v /home/zuul/src/opendev.org/opendev/system-config/playbooks/service-dstatlogger.yaml
|
shell: 'ansible-playbook -v /home/zuul/src/opendev.org/opendev/system-config/playbooks/service-dstatlogger.yaml 2>&1 | tee /var/log/ansible/service-dstatlogger.yaml.log'
|
||||||
|
|
||||||
- name: Run playbook
|
- name: Run playbook
|
||||||
when: run_playbooks is defined
|
when: run_playbooks is defined
|
||||||
loop: "{{ run_playbooks }}"
|
loop: "{{ run_playbooks }}"
|
||||||
command: "ansible-playbook -f 50 -v /home/zuul/src/opendev.org/opendev/system-config/{{ item }}"
|
shell: "ansible-playbook -f 50 -v /home/zuul/src/opendev.org/opendev/system-config/{{ item }} 2>&1 | tee /var/log/ansible/{{ item | basename }}.log"
|
||||||
|
|
||||||
|
- name: Build list of playbook logs
|
||||||
|
find:
|
||||||
|
paths: '/var/log/ansible'
|
||||||
|
patterns: '*.yaml.log'
|
||||||
|
register: _run_playbooks_logs
|
||||||
|
|
||||||
|
- name: Encrypt playbook logs
|
||||||
|
when: run_playbooks is defined
|
||||||
|
include_role:
|
||||||
|
name: encrypt-logs
|
||||||
|
vars:
|
||||||
|
encrypt_logs_files: '{{ _run_playbooks_logs.files | map(attribute="path") | list }}'
|
||||||
|
encrypt_logs_artifact_path: 'bridge.openstack.org/ansible'
|
||||||
|
encrypt_logs_download_script_path: '/var/log/ansible'
|
||||||
|
|
||||||
- name: Run test playbook
|
- name: Run test playbook
|
||||||
when: run_test_playbook is defined
|
when: run_test_playbook is defined
|
||||||
shell: "ANSIBLE_ROLES_PATH=/home/zuul/src/opendev.org/opendev/system-config/playbooks/roles ansible-playbook -v /home/zuul/src/opendev.org/opendev/system-config/{{ run_test_playbook }}"
|
shell: "ANSIBLE_ROLES_PATH=/home/zuul/src/opendev.org/opendev/system-config/playbooks/roles ansible-playbook -v /home/zuul/src/opendev.org/opendev/system-config/{{ run_test_playbook }} 2>&1 | tee /var/log/ansible/{{ run_test_playbook | basename }}.log"
|
||||||
|
|
||||||
- name: Generate testinfra extra data fixture
|
- name: Generate testinfra extra data fixture
|
||||||
set_fact:
|
set_fact:
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
'{{ zuul.project.src_dir }}/test-results.html': logs
|
'{{ zuul.project.src_dir }}/test-results.html': logs
|
||||||
'{{ zuul.project.src_dir }}/inventory/base/gate-hosts.yaml': logs
|
'{{ zuul.project.src_dir }}/inventory/base/gate-hosts.yaml': logs
|
||||||
'/var/log/screenshots': logs
|
'/var/log/screenshots': logs
|
||||||
|
'/var/log/ansible': logs
|
||||||
|
|
||||||
# Note: the following two jobs implement the variant-based multiple
|
# Note: the following two jobs implement the variant-based multiple
|
||||||
# inheritance trick. Both of these variants will always apply,
|
# inheritance trick. Both of these variants will always apply,
|
||||||
|
Loading…
Reference in New Issue
Block a user