mirror of
https://github.com/CERT-Polska/drakvuf-sandbox.git
synced 2026-10-11 22:00:00 +00:00
Bump version: v0.21.0 (#1220)
This commit is contained in:
+2
-1
@@ -27,7 +27,7 @@ copyright = '2025, CERT Polska'
|
||||
author = 'CERT Polska'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = 'v0.20.0'
|
||||
release = 'v0.21.0'
|
||||
|
||||
latex_engine = 'xelatex'
|
||||
|
||||
@@ -41,6 +41,7 @@ def setup(app):
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx_rtd_theme",
|
||||
"sphinx.ext.autosectionlabel",
|
||||
"sphinx.ext.graphviz",
|
||||
]
|
||||
|
||||
|
||||
@@ -2,6 +2,36 @@
|
||||
What's changed, how to upgrade?
|
||||
===============================
|
||||
|
||||
v0.21.0
|
||||
-------
|
||||
|
||||
This release comes with multiple bugfixes and additional web application features.
|
||||
|
||||
Most important changes:
|
||||
|
||||
- Web: All analysis files can be downloaded as .zip archive
|
||||
- Added offline HTML analysis reports that can be downloaded and shared with someone without direct access to Drakvuf Sandbox
|
||||
- Support for "shellexec" injection method and runas elevation
|
||||
- Improved iptables rules, added conntrack drop after analysis
|
||||
|
||||
Known issues:
|
||||
|
||||
- Analysis may hang on Windows 7 with pre-installed Powershell 2.0. It's recommended to upgrade Powershell to at least 5.1. See also `the issue #1199 on Github <https://github.com/CERT-Polska/drakvuf-sandbox/issues/1199>`_.
|
||||
|
||||
This version was tested using `DRAKVUF v1.1-8107f115 <https://github.com/tklengyel/drakvuf/commit/8107f115d35311d483e314f48c3b172e094ec6e9>`_ and may not work correctly with older versions.
|
||||
It's recommended to upgrade DRAKVUF to this version or newer before running analyses.
|
||||
|
||||
After upgrading Drakvuf engine, it's also recommended to regenerate VMI profiles to include the 64-bit .NET ``clr.dll`` profile. This can be
|
||||
done by doing the following operations:
|
||||
|
||||
- shut down all of the workers and ensure that all of the guest VMs are stopped
|
||||
- run ``drakrun modify-vm0 begin`` to start VM-0
|
||||
- after ensuring that VM started correctly, run ``drakrun modify-vm0 commit`` to create a new snapshot and refresh the VMI profiles.
|
||||
|
||||
It's recommended to read the :ref:`Managing snapshots` documentation section before running these operations.
|
||||
|
||||
Complete changelog can be found here: `v0.21.0 changelog <https://github.com/CERT-Polska/drakvuf-sandbox/releases/tag/v0.21.0>`_.
|
||||
|
||||
v0.20.0
|
||||
-------
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.20.0"
|
||||
__version__ = "0.21.0"
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@drakvuf-sandbox/web",
|
||||
"version": "0.20.0",
|
||||
"version": "0.21.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@drakvuf-sandbox/web",
|
||||
"version": "0.20.0",
|
||||
"version": "0.21.0",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@drakvuf-sandbox/web",
|
||||
"private": true,
|
||||
"version": "0.20.0",
|
||||
"version": "0.21.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"files": {
|
||||
"drakrun/web/frontend/package.json": "\"version\": \"$VERSION\"",
|
||||
"drakrun/web/frontend/package-lock.json": "\"version\": \"$VERSION\"",
|
||||
"drakrun/version.py": "__version__ = \"$VERSION\"",
|
||||
"docs/conf.py": "release = 'v$VERSION'"
|
||||
},
|
||||
"regex": "(\\d+\\.\\d+\\.\\d+(?:-(post|dev|alpha|beta|rc)[0-9]?)?)"
|
||||
}
|
||||
+244
-30
@@ -1,27 +1,161 @@
|
||||
#! /usr/bin/env python3
|
||||
# bump_version.py 0.1.0
|
||||
# Copyright (c) 2026, CERT Polska. All rights reserved.
|
||||
#
|
||||
# This file's content is free software; you can redistribute and/or
|
||||
# modify it under the terms of the *BSD 3-Clause "New" or "Revised"
|
||||
#
|
||||
# bump_version 0.2.0 - Utility for version bump and management
|
||||
#
|
||||
# This file automates the version bumping in various files and
|
||||
# assists CI workflow in determining the version information.
|
||||
#
|
||||
# Usage: python3 bump_version.py --help
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import json
|
||||
import itertools
|
||||
import tomllib
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG = json.loads((Path(__file__).parent / "bump_version.json").read_text())
|
||||
CURRENT_DIR = Path.cwd()
|
||||
SCRIPT_ROOT = Path(__file__).parent
|
||||
PROJECT_ROOT = SCRIPT_ROOT.parent
|
||||
|
||||
CONFIG = tomllib.loads((SCRIPT_ROOT / "bump_version.toml").read_text())
|
||||
VERSION_FILES = {
|
||||
(CURRENT_DIR / path): pattern for path, pattern in CONFIG["files"].items()
|
||||
(PROJECT_ROOT / path): patterns for path, patterns in CONFIG["files"].items()
|
||||
}
|
||||
VERSION_REGEX = CONFIG["regex"]
|
||||
# https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers
|
||||
VERSION_REGEX = (
|
||||
r"(?P<version>"
|
||||
r"(?P<major>\d+)[.]"
|
||||
r"(?P<minor>\d+)[.]"
|
||||
r"(?P<patch>\d+)"
|
||||
r"(?P<pre_release>(?:a|b|rc)\d+)?"
|
||||
r"(?P<post_release>(?:.post)\d+)?"
|
||||
r"(?P<dev_release>(?:.dev)\d+)?"
|
||||
r")"
|
||||
)
|
||||
CHANGELOG_FILE = PROJECT_ROOT / "CHANGELOG.md"
|
||||
|
||||
|
||||
def main(new_version):
|
||||
def get_changelog_header_pattern() -> str:
|
||||
"""Load and validate changelog header from configuration"""
|
||||
header_pattern = CONFIG.get("changelog", {}).get("header_pattern")
|
||||
|
||||
if not header_pattern:
|
||||
raise RuntimeError(
|
||||
"[changelog].header_pattern is undefined in bump_version.toml"
|
||||
)
|
||||
|
||||
if not "$VERSION" in header_pattern:
|
||||
raise RuntimeError("[changelog].header_pattern must have $VERSION mark")
|
||||
|
||||
return header_pattern
|
||||
|
||||
|
||||
def load_changelog() -> str:
|
||||
"""Load changelog file contents"""
|
||||
if not CHANGELOG_FILE.exists():
|
||||
raise RuntimeError(f"Changelog file '{CHANGELOG_FILE}' doesn't exist")
|
||||
|
||||
return CHANGELOG_FILE.read_text()
|
||||
|
||||
|
||||
def extract_changelog() -> dict[str, str]:
|
||||
"""Load changelog entries for each version"""
|
||||
changelog = load_changelog()
|
||||
header_pattern = get_changelog_header_pattern()
|
||||
|
||||
version_pattern = header_pattern.replace("$VERSION", VERSION_REGEX)
|
||||
matches = re.finditer(version_pattern, changelog)
|
||||
entries = {}
|
||||
for cur_version, next_version in itertools.pairwise(
|
||||
itertools.chain(matches, [None])
|
||||
):
|
||||
start = changelog.index("\n", cur_version.end())
|
||||
end = (
|
||||
len(changelog)
|
||||
if not next_version
|
||||
else changelog.rindex("\n", start, next_version.start())
|
||||
)
|
||||
entries[cur_version.group("version")] = changelog[start:end].strip()
|
||||
return entries
|
||||
|
||||
|
||||
def find_latest_version() -> str:
|
||||
"""Find current latest version from version files"""
|
||||
current_version = None
|
||||
|
||||
for path in VERSION_FILES.keys():
|
||||
if not path.exists():
|
||||
raise RuntimeError(f"File {path} is missing in project root dir?")
|
||||
|
||||
content = path.read_text()
|
||||
|
||||
for pattern in VERSION_FILES[path]:
|
||||
pattern = pattern.replace("$VERSION", VERSION_REGEX)
|
||||
version = next(re.finditer(pattern, content)).group("version")
|
||||
if current_version is not None and version != current_version:
|
||||
raise RuntimeError(
|
||||
f"{path} contains different version than other files "
|
||||
f"({version} != {current_version})"
|
||||
)
|
||||
current_version = version
|
||||
if not current_version:
|
||||
raise RuntimeError("Version files not found or undefined")
|
||||
return current_version
|
||||
|
||||
|
||||
def increment_version(version: str, major: bool, minor: bool, patch: bool) -> str:
|
||||
"""
|
||||
Increment version number.
|
||||
|
||||
Raises RuntimeError when version is a pre-release or dev-release.
|
||||
Major, minor and patch flags are treated as mutually exclusive.
|
||||
|
||||
:param version: Version number to increment
|
||||
:param major: Increment major
|
||||
:param minor: Increment minor
|
||||
:param patch: Increment patch
|
||||
:return: Incremented version number
|
||||
"""
|
||||
version_match = re.fullmatch(VERSION_REGEX, version)
|
||||
if not version_match:
|
||||
raise RuntimeError(f"'{version}' doesn't match the regex: {VERSION_REGEX}")
|
||||
if version_match.group("pre_release") or version_match.group("dev_release"):
|
||||
raise RuntimeError(
|
||||
f"'{version}' is a pre-release version, please provide version manually"
|
||||
)
|
||||
|
||||
vmajor = int(version_match.group("major"))
|
||||
vminor = int(version_match.group("minor"))
|
||||
vpatch = int(version_match.group("patch"))
|
||||
if major:
|
||||
vmajor = vmajor + 1
|
||||
vminor = vpatch = 0
|
||||
elif minor:
|
||||
vminor = vminor + 1
|
||||
vpatch = 0
|
||||
elif patch:
|
||||
vpatch = vpatch + 1
|
||||
return f"{vmajor}.{vminor}.{vpatch}"
|
||||
|
||||
|
||||
def bump_version(new_version: str) -> None:
|
||||
"""
|
||||
Interactively bumps version, prompting user for confirmation
|
||||
|
||||
:param new_version: New version specified by user
|
||||
"""
|
||||
input_files = {}
|
||||
output_files = {}
|
||||
old_version = None
|
||||
|
||||
if not re.match(fr"^{VERSION_REGEX}$", new_version):
|
||||
print(f"[!] '{new_version}' doesn't match the regex: {VERSION_REGEX}")
|
||||
return
|
||||
if not re.fullmatch(VERSION_REGEX, new_version):
|
||||
raise RuntimeError(f"'{new_version}' doesn't match the regex: {VERSION_REGEX}")
|
||||
|
||||
def subst_version(repl):
|
||||
return (
|
||||
@@ -32,34 +166,55 @@ def main(new_version):
|
||||
|
||||
for path in VERSION_FILES.keys():
|
||||
if not path.exists():
|
||||
print(f"[!] File {str(path)} is missing. Are you in project root dir?")
|
||||
return False
|
||||
raise RuntimeError(f"File {path} is missing in project root dir?")
|
||||
|
||||
with open(path, "r") as f:
|
||||
content = input_files[path] = f.read()
|
||||
content = input_files[path] = path.read_text()
|
||||
|
||||
pattern = VERSION_FILES[path].replace("$VERSION", VERSION_REGEX)
|
||||
print(pattern)
|
||||
version = next(re.finditer(pattern, content)).group(1)
|
||||
output_files[path] = re.sub(pattern, subst_version, content, count=1)
|
||||
for pattern in VERSION_FILES[path]:
|
||||
pattern = pattern.replace("$VERSION", VERSION_REGEX)
|
||||
version = next(re.finditer(pattern, content)).group("version")
|
||||
content = re.sub(pattern, subst_version, content)
|
||||
output_files[path] = content
|
||||
|
||||
if old_version is not None and version != old_version:
|
||||
print(
|
||||
f"[!] {str(path)} contains different version than other files "
|
||||
f"({version} != {old_version})"
|
||||
if old_version is not None and version != old_version:
|
||||
raise RuntimeError(
|
||||
f"{path} contains different version than other files "
|
||||
f"({version} != {old_version})"
|
||||
)
|
||||
old_version = version
|
||||
|
||||
if CHANGELOG_FILE.exists():
|
||||
changelog = load_changelog()
|
||||
header_pattern = get_changelog_header_pattern()
|
||||
changelog_entries = extract_changelog()
|
||||
if new_version not in changelog_entries:
|
||||
if "new_header_pattern" in CONFIG.get("changelog", {}):
|
||||
version_header = CONFIG["changelog"]["new_header_pattern"].replace(
|
||||
"$VERSION", new_version
|
||||
)
|
||||
else:
|
||||
version_header = header_pattern.replace("$VERSION", new_version)
|
||||
input_files[CHANGELOG_FILE] = changelog
|
||||
output_files[CHANGELOG_FILE] = (
|
||||
changelog.rstrip() + "\n\n" + version_header + "\n\n"
|
||||
)
|
||||
old_version = version
|
||||
|
||||
for path in VERSION_FILES.keys():
|
||||
changes = False
|
||||
|
||||
for path in output_files.keys():
|
||||
input_lines = input_files[path].splitlines()
|
||||
output_lines = output_files[path].splitlines()
|
||||
if input_lines == output_lines:
|
||||
print("[*] No changes detected.")
|
||||
return
|
||||
continue
|
||||
changes = True
|
||||
print("=== " + str(path))
|
||||
for line in difflib.unified_diff(input_lines, output_lines, lineterm=""):
|
||||
print(line)
|
||||
|
||||
if not changes:
|
||||
print("[*] No changes detected.")
|
||||
return
|
||||
|
||||
response = ""
|
||||
while response.lower() not in {"y", "n", "yes", "no"}:
|
||||
response = input("[*] Check above diff ^ Is it correct? (y/n): ")
|
||||
@@ -69,12 +224,71 @@ def main(new_version):
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
print("[+] Changes applied!")
|
||||
if CHANGELOG_FILE in output_files:
|
||||
print(
|
||||
"[+] New changelog entry was created. Make sure to fill it before applying version bump."
|
||||
)
|
||||
else:
|
||||
print("[-] Changes discarded.")
|
||||
|
||||
|
||||
def parse_args(args: tuple[str, ...]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(prog=Path(__file__).name)
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
bump = subparsers.add_parser("bump", help="bump the project version")
|
||||
group = bump.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument("--major", action="store_true", help="bump the major version")
|
||||
group.add_argument("--minor", action="store_true", help="bump the minor version")
|
||||
group.add_argument("--patch", action="store_true", help="bump the patch version")
|
||||
group.add_argument(
|
||||
"version", nargs="?", default=None, help="set an explicit version"
|
||||
)
|
||||
|
||||
subparsers.add_parser("version", help="print the current version")
|
||||
|
||||
changelog = subparsers.add_parser("changelog", help="print the changelog")
|
||||
changelog.add_argument(
|
||||
"version", nargs="?", default=None, help="version to show (default: latest)"
|
||||
)
|
||||
|
||||
if not len(args) or args[0] not in [*subparsers.choices, "-h", "--help"]:
|
||||
args = "bump", *args
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
def main(*argv: str) -> int:
|
||||
args = parse_args(argv)
|
||||
|
||||
if args.command == "bump":
|
||||
flag_set = args.major or args.minor or args.patch
|
||||
if flag_set:
|
||||
version = find_latest_version()
|
||||
next_version = increment_version(
|
||||
version, major=args.major, minor=args.minor, patch=args.patch
|
||||
)
|
||||
else:
|
||||
next_version = args.version
|
||||
try:
|
||||
bump_version(next_version)
|
||||
except RuntimeError as e:
|
||||
sys.exit(f"[!] {e!s}")
|
||||
elif args.command == "version":
|
||||
try:
|
||||
print(find_latest_version(), end="")
|
||||
except RuntimeError as e:
|
||||
sys.exit(f"[!] {e!s}")
|
||||
elif args.command == "changelog":
|
||||
try:
|
||||
version = find_latest_version() if args.version is None else args.version
|
||||
changelog = extract_changelog()
|
||||
except RuntimeError as e:
|
||||
sys.exit(f"[!] {e!s}")
|
||||
if version not in changelog:
|
||||
sys.exit(f"[!] {version} not in changelog")
|
||||
print(changelog[version], end="")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not sys.argv[1:]:
|
||||
print("Usage: bump_version.py [new_version]")
|
||||
else:
|
||||
main(sys.argv[1])
|
||||
sys.exit(main(*sys.argv[1:]))
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[files]
|
||||
"drakrun/web/frontend/package.json" = [
|
||||
"\"version\": \"$VERSION\"",
|
||||
]
|
||||
"drakrun/web/frontend/package-lock.json" = [
|
||||
"\"name\": \"@drakvuf-sandbox/web\",\\s+\"version\": \"$VERSION\"",
|
||||
]
|
||||
"drakrun/version.py" = [
|
||||
"__version__ = \"$VERSION\"",
|
||||
]
|
||||
"docs/conf.py" = [
|
||||
"release = 'v$VERSION'",
|
||||
]
|
||||
Reference in New Issue
Block a user