mirror of
https://github.com/mvt-project/mvt.git
synced 2026-10-29 09:16:21 +01:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df540d7c41 | ||
|
|
706e81dff2 | ||
|
|
3ba15716a2 | ||
|
|
8068569dce | ||
|
|
6e9408e30c |
No files matched your search
@@ -14,10 +14,10 @@ env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
# Build each architecture natively, then publish the combined image manifests.
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ${{ matrix.architecture.runner }}
|
||||
runs-on: ubuntu-latest
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -26,15 +26,7 @@ jobs:
|
||||
id-token: write
|
||||
#
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-24.04
|
||||
name: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
name: arm64
|
||||
platform:
|
||||
- dockerfile: "Dockerfile"
|
||||
tag-suffix: ""
|
||||
@@ -69,95 +61,24 @@ jobs:
|
||||
type=raw,enable=${{ github.ref_type == 'tag' }},value=${{ github.ref_name }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,format=long,suffix=${{ matrix.platform.tag-suffix }}
|
||||
# Register emulators so Buildx can build the ARM64 images on the AMD64 runner.
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
# This step sets up some additional capabilities to generate the provenance and sbom attestations
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# Push by digest so architecture builds do not overwrite shared image tags.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
file: ${{ matrix.platform.dockerfile }}
|
||||
context: .
|
||||
platforms: ${{ matrix.architecture.platform }}
|
||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
- name: Export image digest
|
||||
env:
|
||||
DIGEST: ${{ steps.push.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}/digests"
|
||||
touch "${RUNNER_TEMP}/digests/${DIGEST#sha256:}"
|
||||
- name: Upload image digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ matrix.platform.dockerfile }}-${{ matrix.architecture.name }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
publish-manifests:
|
||||
needs: build-and-push-image
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- dockerfile: "Dockerfile"
|
||||
tag-suffix: ""
|
||||
- dockerfile: "Dockerfile.ios"
|
||||
tag-suffix: "-ios"
|
||||
- dockerfile: "Dockerfile.android"
|
||||
tag-suffix: "-android"
|
||||
steps:
|
||||
- name: Download image digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: digests-${{ matrix.platform.dockerfile }}-*
|
||||
path: ${{ runner.temp }}/digests
|
||||
merge-multiple: true
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{ is_default_branch }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=raw,enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }},value=stable,suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=raw,enable=${{ github.event_name == 'release' }},value=${{ github.event.release.tag_name }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=raw,enable=${{ github.ref_type == 'tag' }},value=${{ github.ref_name }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,format=long,suffix=${{ matrix.platform.tag-suffix }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Publish multi-platform image
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
env:
|
||||
METADATA_JSON: ${{ steps.meta.outputs.json }}
|
||||
run: |
|
||||
mapfile -t tags < <(jq -r '.tags[]' <<< "$METADATA_JSON")
|
||||
tag_args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
tag_args+=(--tag "$tag")
|
||||
done
|
||||
images=()
|
||||
for digest in *; do
|
||||
images+=("${REGISTRY}/${IMAGE_NAME}@sha256:${digest}")
|
||||
done
|
||||
test "${#images[@]}" -eq 2
|
||||
docker buildx imagetools create "${tag_args[@]}" "${images[@]}"
|
||||
docker buildx imagetools inspect "${tags[0]}"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
+20
@@ -18,8 +18,28 @@ markdown_extensions:
|
||||
use_pygments: true
|
||||
theme:
|
||||
name: material
|
||||
logo: mvt.png
|
||||
favicon: favicon.png
|
||||
features:
|
||||
- tabs
|
||||
palette:
|
||||
- media: "(prefers-color-scheme)"
|
||||
primary: black
|
||||
toggle:
|
||||
icon: material/link
|
||||
name: Switch to light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: black
|
||||
toggle:
|
||||
icon: material/toggle-switch
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: black
|
||||
toggle:
|
||||
icon: material/toggle-switch-off
|
||||
name: Switch to system preference
|
||||
plugins:
|
||||
- search
|
||||
- mkdocstrings
|
||||
|
||||
+12
-4
@@ -160,7 +160,9 @@ def check_adb(ctx):
|
||||
"zone is read from persist.sys.timezone in the bugreport itself."
|
||||
),
|
||||
)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
||||
@click.option(
|
||||
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||
)
|
||||
@click.argument("BUGREPORT_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_bugreport(
|
||||
@@ -235,7 +237,9 @@ def check_bugreport(
|
||||
)
|
||||
@click.option("--non-interactive", "-n", is_flag=True, help=HELP_MSG_NONINTERACTIVE)
|
||||
@click.option("--backup-password", "-p", help=HELP_MSG_ANDROID_BACKUP_PASSWORD)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
||||
@click.option(
|
||||
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||
)
|
||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_backup(
|
||||
@@ -310,7 +314,9 @@ def check_backup(
|
||||
)
|
||||
@click.option("--non-interactive", "-n", is_flag=True, help=HELP_MSG_NONINTERACTIVE)
|
||||
@click.option("--backup-password", "-p", help=HELP_MSG_ANDROID_BACKUP_PASSWORD)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
||||
@click.option(
|
||||
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||
)
|
||||
@click.argument("ANDROIDQF_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_androidqf(
|
||||
@@ -397,7 +403,9 @@ def check_androidqf(
|
||||
"time instead of UTC."
|
||||
),
|
||||
)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
||||
@click.option(
|
||||
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||
)
|
||||
@click.argument("LOGS_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_intrusion_logs(
|
||||
|
||||
@@ -27,6 +27,8 @@ from .modules.backup import BACKUP_MODULES
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
MAX_BACKUP_PASSWORD_ATTEMPTS = 3
|
||||
|
||||
|
||||
class InvalidAndroidBackup(Exception):
|
||||
pass
|
||||
@@ -72,6 +74,15 @@ class CmdAndroidCheckBackup(Command):
|
||||
self.__tar: Optional[tarfile.TarFile] = None
|
||||
self.__files: List[str] = []
|
||||
|
||||
def _get_backup_password(self) -> str:
|
||||
password = prompt_or_load_android_backup_password(log, self.module_options)
|
||||
if not password:
|
||||
if self.sub_command:
|
||||
raise InvalidAndroidBackup("No backup password provided")
|
||||
log.critical("No backup password provided.")
|
||||
sys.exit(1)
|
||||
return password
|
||||
|
||||
def from_ab(self, ab_file_bytes: bytes) -> None:
|
||||
self.__type = "ab"
|
||||
header = parse_ab_header(ab_file_bytes)
|
||||
@@ -84,28 +95,40 @@ class CmdAndroidCheckBackup(Command):
|
||||
sys.exit(1)
|
||||
|
||||
password = None
|
||||
# A password typed at the prompt is asked for again if it is wrong.
|
||||
# One given on the command line or in the environment is not.
|
||||
max_attempts = 1
|
||||
if header["encryption"] != "none":
|
||||
password = prompt_or_load_android_backup_password(log, self.module_options)
|
||||
if not password:
|
||||
if not self.module_options.get("backup_password", None):
|
||||
max_attempts = MAX_BACKUP_PASSWORD_ATTEMPTS
|
||||
password = self._get_backup_password()
|
||||
attempt = 1
|
||||
while True:
|
||||
try:
|
||||
tardata = parse_backup_file(ab_file_bytes, password=password)
|
||||
break
|
||||
except InvalidBackupPassword:
|
||||
if attempt < max_attempts:
|
||||
attempt += 1
|
||||
log.error(
|
||||
"Invalid backup password, try again (attempt %d of %d)",
|
||||
attempt,
|
||||
max_attempts,
|
||||
)
|
||||
password = self._get_backup_password()
|
||||
continue
|
||||
if self.sub_command:
|
||||
raise InvalidAndroidBackup("No backup password provided")
|
||||
log.critical("No backup password provided.")
|
||||
raise InvalidAndroidBackup("Invalid backup password")
|
||||
log.critical("Invalid backup password")
|
||||
sys.exit(1)
|
||||
except AndroidBackupParsingError as exc:
|
||||
if self.sub_command:
|
||||
raise InvalidAndroidBackup(
|
||||
f"Impossible to parse this backup file: {exc}"
|
||||
) from exc
|
||||
log.critical("Impossible to parse this backup file: %s", exc)
|
||||
log.critical("Please use Android Backup Extractor (ABE) instead")
|
||||
sys.exit(1)
|
||||
try:
|
||||
tardata = parse_backup_file(ab_file_bytes, password=password)
|
||||
except InvalidBackupPassword:
|
||||
if self.sub_command:
|
||||
raise InvalidAndroidBackup("Invalid backup password")
|
||||
log.critical("Invalid backup password")
|
||||
sys.exit(1)
|
||||
except AndroidBackupParsingError as exc:
|
||||
if self.sub_command:
|
||||
raise InvalidAndroidBackup(
|
||||
f"Impossible to parse this backup file: {exc}"
|
||||
) from exc
|
||||
log.critical("Impossible to parse this backup file: %s", exc)
|
||||
log.critical("Please use Android Backup Extractor (ABE) instead")
|
||||
sys.exit(1)
|
||||
|
||||
dbytes = io.BytesIO(tardata)
|
||||
try:
|
||||
|
||||
+9
-3
@@ -278,7 +278,9 @@ def extract_key(password, key_file, backup_path):
|
||||
help=HELP_MSG_LOAD_MODULE,
|
||||
)
|
||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
||||
@click.option(
|
||||
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||
)
|
||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_backup(
|
||||
@@ -347,7 +349,9 @@ def check_backup(
|
||||
help=HELP_MSG_LOAD_MODULE,
|
||||
)
|
||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
||||
@click.option(
|
||||
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||
)
|
||||
@click.argument("DUMP_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_fs(
|
||||
@@ -416,7 +420,9 @@ def check_fs(
|
||||
help=HELP_MSG_LOAD_MODULE,
|
||||
)
|
||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
||||
@click.option(
|
||||
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||
)
|
||||
@click.argument("SYSDIAGNOSE_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_sysdiagnose(
|
||||
|
||||
@@ -1224,6 +1224,10 @@
|
||||
"version": "18.7.10",
|
||||
"build": "22H374"
|
||||
},
|
||||
{
|
||||
"version": "18.7.11",
|
||||
"build": "22H384"
|
||||
},
|
||||
{
|
||||
"version": "26",
|
||||
"build": "23A341"
|
||||
@@ -1283,5 +1287,13 @@
|
||||
{
|
||||
"version": "27.0.1",
|
||||
"build": "24A446"
|
||||
},
|
||||
{
|
||||
"version": "27.2",
|
||||
"build": "24B91020"
|
||||
},
|
||||
{
|
||||
"version": "27.2",
|
||||
"build": "24B119"
|
||||
}
|
||||
]
|
||||
@@ -59,6 +59,66 @@ class TestCheckAndroidBackupCommand:
|
||||
assert result.exit_code == 1
|
||||
assert "Invalid backup password" in caplog.text
|
||||
|
||||
def test_check_encrypted_backup_prompt_retry(self, mocker):
|
||||
"""A wrong password at the prompt is asked for again"""
|
||||
prompt_mock = mocker.patch(
|
||||
"mvt.android.modules.backup.helpers.prompt_password",
|
||||
side_effect=["wrong_password", TEST_BACKUP_PASSWORD],
|
||||
)
|
||||
runner = CliRunner()
|
||||
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||
result = runner.invoke(check_backup, [path])
|
||||
|
||||
assert prompt_mock.call_count == 2
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_check_encrypted_backup_prompt_gives_up(self, mocker, caplog):
|
||||
"""The prompt gives up after three wrong passwords"""
|
||||
prompt_mock = mocker.patch(
|
||||
"mvt.android.modules.backup.helpers.prompt_password",
|
||||
return_value="wrong_password",
|
||||
)
|
||||
runner = CliRunner()
|
||||
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||
|
||||
with caplog.at_level(logging.CRITICAL):
|
||||
result = runner.invoke(check_backup, [path])
|
||||
|
||||
assert prompt_mock.call_count == 3
|
||||
assert result.exit_code == 1
|
||||
assert "Invalid backup password" in caplog.text
|
||||
|
||||
def test_check_encrypted_backup_prompt_retry_empty(self, mocker, caplog):
|
||||
"""An empty answer on a retry ends the check"""
|
||||
prompt_mock = mocker.patch(
|
||||
"mvt.android.modules.backup.helpers.prompt_password",
|
||||
side_effect=["wrong_password", ""],
|
||||
)
|
||||
runner = CliRunner()
|
||||
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||
|
||||
with caplog.at_level(logging.CRITICAL):
|
||||
result = runner.invoke(check_backup, [path])
|
||||
|
||||
assert prompt_mock.call_count == 2
|
||||
assert result.exit_code == 1
|
||||
assert "No backup password provided" in caplog.text
|
||||
|
||||
def test_check_encrypted_backup_cli_invalid_no_prompt(self, mocker):
|
||||
"""A password given on the command line is not asked for again"""
|
||||
prompt_mock = mocker.patch(
|
||||
"mvt.android.modules.backup.helpers.prompt_password",
|
||||
return_value=TEST_BACKUP_PASSWORD,
|
||||
)
|
||||
runner = CliRunner()
|
||||
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||
result = runner.invoke(
|
||||
check_backup, ["--backup-password", "invalid_password", path]
|
||||
)
|
||||
|
||||
assert prompt_mock.call_count == 0
|
||||
assert result.exit_code == 1
|
||||
|
||||
def test_check_encrypted_backup_env(self, mocker):
|
||||
"""Provide password as environment variable"""
|
||||
prompt_mock = mocker.patch(
|
||||
|
||||
@@ -100,8 +100,8 @@ class TestVerboseOnTheCheckCommands:
|
||||
assert result.exit_code == 0
|
||||
assert _console_level() == logging.DEBUG
|
||||
|
||||
def test_the_command_option_says_it_is_kept_for_compatibility(self):
|
||||
def test_the_command_option_is_hidden_from_the_help(self):
|
||||
result = CliRunner().invoke(ios_cli, [*OFFLINE, "check-backup", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "kept for compatibility" in result.output
|
||||
assert "--verbose" not in result.output
|
||||
Reference in new issue
Block a user