Skip to content
4 changes: 4 additions & 0 deletions src/roles/iop_core/tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
ansible.builtin.include_role:
name: iop_cvemap_downloader

- name: Deploy IOP VEX Downloader
ansible.builtin.include_role:
name: iop_vex_downloader

- name: Deploy IOP VMAAS service
ansible.builtin.include_role:
name: iop_vmaas
Expand Down
4 changes: 4 additions & 0 deletions src/roles/iop_vex_downloader/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
iop_vex_downloader_base_url: "https://security.access.redhat.com/data/csaf/v2/vex/"
iop_vex_downloader_output_dir: "/var/www/html/pub/iop/data/csaf/v2/vex/"
iop_vex_downloader_timer_interval: "1d"
9 changes: 9 additions & 0 deletions src/roles/iop_vex_downloader/files/iop-vex-download.path
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=Path unit for vex-latest.tar.zst manual file watcher

[Path]
PathChanged=/var/lib/foreman/vex-latest.tar.zst
PathModified=/var/lib/foreman/vex-latest.tar.zst

[Install]
WantedBy=multi-user.target
10 changes: 10 additions & 0 deletions src/roles/iop_vex_downloader/handlers/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: Restart vex download timer
ansible.builtin.systemd:
name: iop-vex-download.timer
state: restarted

- name: Restart vex download path
ansible.builtin.systemd:
name: iop-vex-download.path
state: restarted
65 changes: 65 additions & 0 deletions src/roles/iop_vex_downloader/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
- name: Install vex download script
ansible.builtin.template:
src: iop-vex-downloader.sh.j2
dest: /usr/local/bin/iop-vex-downloader.sh
owner: root
group: root
mode: '0755'

- name: Create vex download service unit
ansible.builtin.copy:
dest: /etc/systemd/system/iop-vex-download.service
content: |
[Unit]
Description=Manages vex-latest.tar.zst for IoP Vulnerability
After=network-online.target iop-core-gateway.service
Wants=network-online.target iop-core-gateway.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/iop-vex-downloader.sh \
'{{ iop_vex_downloader_base_url }}' \
'{{ iop_vex_downloader_output_dir }}'
User=root
Group=root
mode: '0644'

- name: Create vex download timer unit
ansible.builtin.template:
src: iop-vex-download.timer.j2
dest: /etc/systemd/system/iop-vex-download.timer
mode: '0644'
notify: Restart vex download timer

- name: Create vex download path unit
ansible.builtin.copy:
src: iop-vex-download.path
dest: /etc/systemd/system/iop-vex-download.path
mode: '0644'
notify: Restart vex download path

- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true

- name: Flush handlers
ansible.builtin.meta: flush_handlers

- name: Create directory vex download directory
ansible.builtin.file:
path: '{{ iop_vex_downloader_output_dir }}'
state: directory
mode: "0755"

- name: Enable and start vex download timer
ansible.builtin.systemd:
name: iop-vex-download.timer
enabled: true
state: started

- name: Enable and start vex download path watcher
ansible.builtin.systemd:
name: iop-vex-download.path
enabled: true
state: started
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=Timer for vex-latest.tar.zst download

[Timer]
OnActiveSec=0
OnUnitActiveSec={{ iop_vex_downloader_timer_interval }}

[Install]
WantedBy=timers.target
102 changes: 102 additions & 0 deletions src/roles/iop_vex_downloader/templates/iop-vex-downloader.sh.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/bin/bash

set -euo pipefail

FILENAME_LATEST="archive_latest.txt"
OUTPUT_FILENAME="vex-latest.tar.zst"

URL="${1:-}"
Comment thread
pfreyburg marked this conversation as resolved.
OUTPUT_DIR="${2:-}"


if [[ -z "${URL}" || -z "${OUTPUT_DIR}" ]]; then
echo "Usage: ${0} URL OUTPUT_DIR" >&2
echo "Example: ${0} https://security.access.redhat.com/data/csaf/v2/vex/ /root/download/" >&2
exit 1
fi

URL="${URL%/}/"
OUTPUT_DIR="${OUTPUT_DIR%/}/"

MANUAL_FILE="/var/lib/foreman/vex-latest.tar.zst"

if [[ -f "$MANUAL_FILE" ]]; then
echo "Offline mode: Using manual file from $MANUAL_FILE"

CURRENT_CHECKSUM=$(sha256sum "${MANUAL_FILE}" | cut -d' ' -f1)
STORED_CHECKSUM=$(sha256sum "${OUTPUT_DIR}${OUTPUT_FILENAME}" | cut -d' ' -f1)

if [[ "$CURRENT_CHECKSUM" != "$STORED_CHECKSUM" ]]; then
echo "Copying updated manual file"
cp -Z "$MANUAL_FILE" "${OUTPUT_DIR}${OUTPUT_FILENAME}" && echo "$CURRENT_CHECKSUM" > "$CHECKSUM_FILE"
chmod 644 "${OUTPUT_DIR}${OUTPUT_FILENAME}"
else
echo "Manual file unchanged, skipping"
fi
else
echo "Online mode: Checking for updates from $URL"

FILE_MODTIME="Thu, 01 Jan 1970 00:00:00 +0000"

if [[ -f "${OUTPUT_DIR}${OUTPUT_FILENAME}" ]]; then
FILE_MODTIME=$(date -u -R -r "${OUTPUT_DIR}${OUTPUT_FILENAME}")
fi

#
# \
ARCHIVE_DOWNLOAD_NAME_CONTENT=$(curl \
--silent \
--fail \
--location \
-H "If-Modified-Since: ${FILE_MODTIME}"\
"${URL}${FILENAME_LATEST}")

RET=${?}

if [ "${RET}" -ne 0 ]; then
echo "Error: The tarball's name wasn't downloaded" >&2
exit 1
fi

if [ -z "${ARCHIVE_DOWNLOAD_NAME_CONTENT}" ]; then
echo "The file wasn't updated."
exit 0
fi

TEMP_FILE_ARCH=$(mktemp -t "iop-vex-download.XXXXXX")
TEMP_FILE_ASC=$(mktemp -t "iop-vex-download.asc.XXXXXX")

trap 'rm -f "${TEMP_FILE_ARCH}" "${TEMP_FILE_ASC}"' EXIT

curl --silent \
--fail \
--location \
"${URL}${ARCHIVE_DOWNLOAD_NAME_CONTENT}" \
--output "${TEMP_FILE_ARCH}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you figure out a way to skip the download if the content hasn't changed? The file is too large to be downloaded each time this runs.

There are two options:

  • using an HTTP header, like If-Modified-Since -- needs checking whether the CDN supports it
  • checking the contents archive_latest.txt

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How large is this file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ls -lh download/vex-latest.tar.zst
...   268M Jul 20 11:03 download/vex-latest.tar.zst

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I edited the script so now it is downloading only when content is changed. It uses If-Modified-Since


RET=${?}

if [ "${RET}" -ne 0 ]; then
echo "Error: Downloading of tarball failed. Curl failed with code ${RET}" >&2
exit 2
fi

curl --silent \
--fail \
--location \
"${URL}${ARCHIVE_DOWNLOAD_NAME_CONTENT}.asc" \
--output "${TEMP_FILE_ASC}"

RET=${?}

if [ "${RET}" -ne 0 ]; then
echo "Error: Downloading of tarball's signature failed. Curl failed with code ${RET}" >&2
exit 3
fi

mv -Z "${TEMP_FILE_ARCH}" "${OUTPUT_DIR}${OUTPUT_FILENAME}"
mv -Z "${TEMP_FILE_ASC}" "${OUTPUT_DIR}${OUTPUT_FILENAME}.asc"

chmod 644 "${OUTPUT_DIR}${OUTPUT_FILENAME}"
chmod 644 "${OUTPUT_DIR}${OUTPUT_FILENAME}.asc"
fi
55 changes: 55 additions & 0 deletions tests/iop/test_vex_downloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import pytest

pytestmark = pytest.mark.feature("iop")


def test_vex_download_script(server):
script = server.file("/usr/local/bin/iop-vex-downloader.sh")
assert script.exists
assert script.is_file
assert script.mode == 0o755


def test_vex_download_service_unit(server):
unit = server.file("/etc/systemd/system/iop-vex-download.service")
assert unit.exists
assert unit.is_file

content = unit.content_string
assert "Type=oneshot" in content
assert "iop-vex-downloader.sh" in content
assert "iop-core-gateway.service" in content


def test_vex_download_timer_unit(server):
unit = server.file("/etc/systemd/system/iop-vex-download.timer")
assert unit.exists
assert unit.is_file

content = unit.content_string
assert "OnActiveSec=0" in content
assert "OnUnitActiveSec=7d" in content
assert "WantedBy=timers.target" in content


def test_vex_download_timer_enabled(server):
timer = server.service("iop-vex-download.timer")
assert timer.is_enabled
assert timer.is_running


def test_vex_download_path_unit(server):
unit = server.file("/etc/systemd/system/iop-vex-download.path")
assert unit.exists
assert unit.is_file

content = unit.content_string
assert "PathChanged=/var/lib/foreman/vex-latest.tar.zst" in content
assert "PathModified=/var/lib/foreman/vex-latest.tar.zst" in content
assert "WantedBy=multi-user.target" in content


def test_vex_download_path_enabled(server):
path = server.service("iop-vex-download.path")
assert path.is_enabled
assert path.is_running
Loading