Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QWT support #181

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
12d3f67
Add support for Windows executor and build
omeg Nov 6, 2024
9bdf7c6
windows: add support for code signing
omeg Nov 15, 2024
b964409
windows: improve unattended OS installation
omeg Nov 15, 2024
b091107
Fix typos in README
omeg Nov 15, 2024
212940e
windows: fix shellcheck warnings in scripts
omeg Nov 16, 2024
b6501c3
windows: fix download script call
omeg Nov 16, 2024
37bbc89
windows: untabify autounattend.xml
omeg Nov 16, 2024
8390740
windows: generate random user password during worker VM setup
omeg Nov 17, 2024
5bad846
windows: fix existing file check in get-files.sh
omeg Nov 17, 2024
5ac9452
windows: fix missing configuration option in README
omeg Nov 19, 2024
d8aab4e
windows: move the CONFIGURATION placeholder from executor to build st…
omeg Nov 19, 2024
298fe55
windows: automatically create loop device for EWDK
omeg Nov 19, 2024
0d05f14
windows: edit the installation iso in a dispvm
omeg Nov 24, 2024
c8b9987
windows: update RPC policy files
omeg Nov 24, 2024
10f4ac5
windows: refactor signing
omeg Nov 25, 2024
12fe446
windows: timestamp when code signing
omeg Nov 26, 2024
2f46e61
windows: support build via qrexec services
omeg Dec 18, 2024
83729a5
windows: add scripts for local build
omeg Dec 18, 2024
db70cbf
windows: remove redundant code
omeg Dec 18, 2024
eb90a85
windows: add note about MS Defender in the readme
omeg Dec 20, 2024
84e4f85
windows: remove unneeded checks of vm status to improve performance
omeg Dec 20, 2024
26e4737
windows: reword 'mount' to 'attach' for EWDK loopback device
omeg Dec 20, 2024
a5ccf6f
windows: create local .distfiles only if necessary
omeg Dec 20, 2024
b965b0f
windows: fix testsign switch in the build plugin
omeg Dec 20, 2024
382d11b
windows: improve copy_out performance
omeg Dec 24, 2024
10abbea
windows: fix local build
omeg Jan 3, 2025
7713060
windows: fix local build compatibility in build_windows
omeg Jan 3, 2025
03a9289
windows: don't copy qrexec handlers on every command
omeg Feb 10, 2025
a52949c
windows: fix copy-out for no-target projects
omeg Feb 10, 2025
c08a8bf
Fix formatting
omeg Feb 11, 2025
7eef505
windows: remove debug code
omeg Feb 11, 2025
c25d8f5
windows: fix mypy warnings
omeg Feb 11, 2025
d74a23f
windows: update CI dependencies
omeg Feb 11, 2025
d9a809e
Fix CI config
omeg Feb 12, 2025
977fe2b
windows: fix shellcheck warnings in rpc files
omeg Feb 12, 2025
d9984d9
windows: fix review issues
omeg Feb 20, 2025
459a5d9
Refactor WindowsExecutor into WindowsQubesExecutor and SSHWindowsExec…
omeg Feb 26, 2025
819b714
windows: sanitize_line() compatibility
omeg Feb 26, 2025
279996b
windows: remove qubesadmin dependency
omeg Feb 27, 2025
5f3210a
windows: update README
omeg Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 142 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ action on sources, like cloning and verifying Git repos, rendering a SPEC
file, generating SRPM or Debian source packages, a new cage is used. Only the
signing, publishing, and uploading processes are executed locally outside a
cage. (This will be improved in the future.) For now, only Docker, Podman,
Local, and Qubes executors are available.
Local, Qubes and Windows executors are available.


## Dependencies
Expand Down Expand Up @@ -131,6 +131,59 @@ $ qvm-prefs work-qubesos default_dispvm qubes-builder-dvm
```


## Windows executors and building Windows Tools

There are two different Windows executors: `SSHWindowsExecutor` and `WindowsQubesExecutor`.
Prerequisites for both executors are a superset of the Qubes executor (see above).
For code signing you need `osslsigncode` installed in the Linux disposable template
and the signing vault qube (see below).

### `SSHWindowsExecutor`

This executor is meant for development, it uses SSH to communicate with a Windows system
that is used for building. Scripts that can automatically create such qube are found
in the `tools/windows` directory (they require `genisoimage` installed in the disposable template
(`qubes-builder-dvm`)). You will need an unmodified Windows 10/11 installation iso and about
50GiB of disk space.

First, create an edited installation iso by running `generate-iso.sh`. This script
downloads prerequisites (OpenSSH server for Windows and Microsoft EWDK iso) and prepares
the installation image for unattended Windows installation. After the image is generated,
run `dom0/create-vm.sh` in dom0 to actually create and configure the worker qube (passing
the generated installation image as a `--iso` parameter). After the script finishes, the worker
qube is ready to use by the builder.

The worker qube has outbound network connections blocked in the firewall, this is configured
by the `create-vm.sh` script. An ssh key is generated by the `generate-iso.sh` script,
the private part is saved in `~/.ssh/win-build.key` by default while the public part
is copied to the generated Windows installation image.

You can also use any SSH-accessible Windows machine instead.

### `WindowsQubesExecutor`

This executor works in the same manner as the Linux Qubes executor. It requires a Windows
disposable template with Qubes Windows Tools installed (you can use the SSH executor first
to build QWT).

### General information

It is recommended to turn off Microsoft Defender in the worker qube (especially real-time
protection) because it slows down building significantly. This is not done during unattended
setup because there is no supported way for automating this. (TODO: it enables itself after
restart which is a pain for dispvms).

A separate vault-type qube is needed for code-signing Windows binaries. Let's assume it's named
`vault-sign`. This qube has access to actual signing keys used, either production ones (TODO:
in a HSM), or ephemeral self-signed keys. Communication with the `vault-sign` qube goes through
Qubes RPC: install RPC service scripts from `rpc/qubes.WinSign.*` in the vault qube (make sure
they are permanent in `/etc/qubes-rpc`, see [bind dirs](https://www.qubes-os.org/doc/bind-dirs/)).
`qubesbuilder.WinSign.Timestamp` needs to be installed in the default Linux disposable template
(`qubes-builder-dvm`). You also need to configure RPC policy in `dom0`, copy
`rpc/policy/51-qubesbuilder-windows.policy` to `/etc/qubes/policy.d` there (make sure the qube
names are correct).


## Build stages

The build process consists of the following stages:
Expand Down Expand Up @@ -160,9 +213,11 @@ Currently, only these are used:
- `source` --- Manages general distribution sources
- `source_rpm` --- Manages RPM distribution sources
- `source_deb` --- Manages Debian distribution sources
- `source_windows` --- Manages Windows sources
- `build` --- Manages general distribution building
- `build_rpm` --- Manages RPM distribution building
- `build_deb` --- Manages Debian distribution building
- `build_windows` --- Manages Windows building (Visual Studio solutions)
- `sign` --- Manages general distribution signing
- `sign_rpm` --- Manages RPM distribution signing
- `sign_deb` --- Manages Debian distribution signing
Expand Down Expand Up @@ -390,9 +445,10 @@ We provide the following list of available keys:
- `vm` --- `vm` package set content.
- `rpm` --- RPM plugins content.
- `deb` --- Debian plugins content.
- `source` --- Fetch and source plugins (`fetch`, `source`, `source_rpm`, and
`source_deb`) content.
- `build` --- Build plugins content (`build`, `build_rpm`, and `build_deb`).
- `windows` --- Windows plugin content.
- `source` --- Fetch and source plugins (`fetch`, `source`, `source_rpm`,
`source_deb` and `source_windows`) content.
- `build` --- Build plugins content (`build`, `build_rpm`, `build_deb` and `build_windows`).
- `create-archive` --- Create source component directory archive (default:
`True` unless `files` is provided and not empty).
- `commands` --- Execute commands before plugin or distribution tools
Expand Down Expand Up @@ -429,6 +485,17 @@ Here is a non-exhaustive list of distribution-specific keys:
- `host-fc32` --- Fedora 32 for the `host` package set content only
- `vm-bullseye` --- Bullseye for the `vm` package set only

`build_windows` specific: all output artifacts for a component need to be specified in
`.qubesbuilder` as lists of paths (relative to component root) with the following keys:
- `bin` --- binaries (`.exe`, `.dll`, `.sys` and all files that can be PE-signed)
- `inc` --- devel header files that are dependencies for other components
- `lib` --- linker libraries that are dependencies for other components

`skip-test-sign` option can be specified to provide a list of binaries that should not be
signed with a test key (only used for the final installer binary currently, since
the self-signed certificate is in the installer itself so the binary can't be verified
if test-signed).

Inside each top level, it defines what plugin entry points like `rpm`, `deb`,
and `source` will take as input. Having both `PACKAGE_SET` and
`PACKAGE_SET-DISTRIBUTION_NAME` with common keys means that it is up to the
Expand All @@ -449,6 +516,8 @@ currently-supported placeholders:
- `@DISTFILES_DIR@` --- Replaced by `/builder/distfiles` (inside a cage)
- `@SOURCE_DIR@` --- Replaced by `/builder/<COMPONENT_NAME>` (inside a cage
where, `<COMPONENT_NAME>` is the component directory name)
- `@CONFIGURATION@` --- `build_windows` specific, replaced by the project configuration
(`Debug` / `Release`)


### Examples
Expand Down Expand Up @@ -675,6 +744,32 @@ distribution tools like `dpkg-*`. This is only available for Debian
distributions and not RPM distributions, as similar processing is currently not
needed.

Here is an example for a Windows component (`core-vchan-xen`):

```yaml
host:
rpm:
build:
- rpm_spec/libvchan.spec
vm:
rpm:
build:
- rpm_spec/libvchan.spec
(...)
windows:
build:
- windows/vs2022/core-vchan-xen.sln
bin:
- windows/vs2022/x64/@CONFIGURATION@/libvchan/libvchan.dll
inc:
- windows/include/libvchan.h
lib:
- windows/vs2022/x64/@CONFIGURATION@/libvchan/libvchan.lib
```

The `build` stage specifies a Visual Studio solution to be built. `bin`, `inc` and `lib` keys
specify output artifacts.

## Qubes builder configuration

Options available in `builder.yml`:
Expand Down Expand Up @@ -742,14 +837,26 @@ Options available in `builder.yml`:
- `templates: str` --- Testing repository for templates at publish stage. This is either `templates-itl-testing` or `templates-community-testing`.

- `executor: Dict` --- Specify default executor to use.
- `type: str` --- Executor type: qubes, docker, podman or local.
- `type: str` --- Executor type: qubes, docker, podman, local or windows.
- `options: Dict`:
- `image: str` --- Container image to use. Specific to docker or podman type.
- `dispvm: str` --- Disposable template VM to use (use `"@dispvm"` to use the calling qube `default_dispvm` property or specify a name).
- `directory: str` --- Base directory for local executor to create temporary directories.
- `clean: bool` --- Clean container, disposable qube or temporary local folder (default `true`).
- `clean-on-error: bool` --- Clean container, disposable qube or temporary local folder if any error occurred. Default is value set by `clean`.

- Options specific to the `windows` and `windows-ssh` executors (see `example-configs/windows-tools.yml`):
- `user: str` --- name of the user account in the worker Windows machine/VM (default: `user`)
- `threads: int` --- number of parallel threads to use for MSBuild (default: 1)

- Options specific to the `windows` executor:
- `dispvm: str` --- name of the disposable Windows template (default: `win-build`)
- `ewdk: str` --- path to the EWDK iso file that will be attached to the worker qube

- Options specific to the `windows-ssh` executor:
- `ssh-key-path: str` --- path to the private ssh key used for communication with the worker machine (default: `~/.ssh/win-build.key`)
- `ssh-ip: str` --- ip address to use when connecting to the worker machine

- `stages: List[str, Dict]` --- List of stages to trigger.
- `<stage_name>: str` --- Stage name.
- `<stage_name>: Dict` --- Stage name provided as dict to override executor to use.
Expand Down Expand Up @@ -859,3 +966,33 @@ For the `fetch` stage, the Qubes executor with disposable template `qubes-builde
For the `build` stage of `vm-fc42`, the Podman executor with container image `fedoraimg` will be used.
For the `sign` stage, the Qubes executor with disposable template `signing-access-dvm` will be used for both `vm-fc42` and `vm-jammy`
For the `prep` stage of `vm-jammy`, the Local executor with base directory `/some/path` will be used.

### Windows-specific build stage options

Options related to Qubes Windows Tools can be specified under the `build` stage of a Windows distribution, like this:

```yaml
distributions:
- vm-win10:
stages:
- build:
configuration: release
sign-qube: vault-sign
sign-key-name: "Qubes Windows Tools"
test-sign: true
executor:
type: windows # or windows-ssh
options:
user: user
threads: 1
dispvm: win-build
ewdk: "dev:loop1"
#ssh-key-path: /home/user/.ssh/win-build.key
#ssh-ip: 10.137.0.20
```

- `configuration: str` --- build configuration (`debug` / `release`) (default: `release`).
- `sign-qube: str` --- name of the vault qube performing code signing, see the Windows executor description above.
- `sign-key-name: str` --- name of the signing key to use. For test keys this becomes the subject of the self-signed certificate.
- `test-sign: bool` --- code signing type, `true` (default) or `false`. Test signing generates ephemeral self-signed
keys for each component. Production signing uses an already existing key signed by a public CA (TODO: HSM).
59 changes: 59 additions & 0 deletions example-configs/windows-tools.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
git:
prefix: omeg/qubes-
branch: omeg/builder-v2
maintainers:
# omeg
- 'CE8060B48282B234AE0A7815D32BF219E67BA830'

#increment-devel-versions: true

debug: true
verbose: true

skip-git-fetch: false

# this is for anything other than building, so source fetching etc
executor:
type: qubes
options:
dispvm: qubes-builder-dvm

# dev only
less-secure-signed-commits-sufficient:
- vmm-xen-windows-pvdrivers
- core-vchan-xen
- windows-utils
- core-qubesdb
- core-agent-windows
- gui-common
- gui-agent-windows
- installer-windows-tools

distributions:
- vm-win10:
stages:
- build:
configuration: release
sign-qube: vault-sign
sign-key-name: "Qubes Windows Tools"
test-sign: true
executor:
#type: windows-ssh
type: windows
options:
dispvm: win-build
user: user
ewdk: tools/windows/ewdk.iso
threads: 1
#ssh-ip: 10.137.0.20
#ssh-key-path: /home/user/.ssh/win-build.key

components:
- vmm-xen-windows-pvdrivers
- core-vchan-xen
- windows-utils
- core-qubesdb
- core-agent-windows
- gui-common
- gui-agent-windows
- installer-windows-tools
5 changes: 4 additions & 1 deletion qubesbuilder/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ def sanitize_line(untrusted_line: bytes):
if 0x20 <= c <= 0x7E:
pass
else:
line[i] = 0x2E
if c == 0x0D: # windows newline
line[i] = 0x20
else:
line[i] = 0x2E
return bytearray(line).decode("ascii")


Expand Down
10 changes: 9 additions & 1 deletion qubesbuilder/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
from qubesbuilder.executors import ExecutorError
from qubesbuilder.executors.container import ContainerExecutor
from qubesbuilder.executors.local import LocalExecutor
from qubesbuilder.executors.qubes import LinuxQubesExecutor
from qubesbuilder.executors.qubes import (
LinuxQubesExecutor,
WindowsQubesExecutor,
)
from qubesbuilder.executors.windows import SSHWindowsExecutor
from qubesbuilder.pluginmanager import PluginManager
from qubesbuilder.plugins import (
DistributionPlugin,
Expand Down Expand Up @@ -572,6 +576,10 @@
executor = LocalExecutor(**executor_options) # type: ignore
elif executor_type == "qubes":
executor = LinuxQubesExecutor(**executor_options) # type: ignore
elif executor_type == "windows":
executor = WindowsQubesExecutor(**executor_options) # type: ignore
elif executor_type == "windows-ssh":
executor = SSHWindowsExecutor(**executor_options) # type: ignore

Check warning on line 582 in qubesbuilder/config.py

View check run for this annotation

Codecov / codecov/patch

qubesbuilder/config.py#L579-L582

Added lines #L579 - L582 were not covered by tests
else:
raise ExecutorError("Cannot determine which executor to use.")
return executor
Expand Down
15 changes: 15 additions & 0 deletions qubesbuilder/distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@

DEBIAN_ARCHITECTURE = {"x86_64": "amd64", "ppc64le": "ppc64el"}

WINDOWS = {
"win10": ("windows", "10"),
"win11": ("windows", "11"),
}


class QubesDistribution:
def __init__(self, distribution: str, **kwargs):
Expand All @@ -65,6 +70,7 @@
is_ubuntu = UBUNTU.get(self.name, None)
is_archlinux = self.name == "archlinux"
is_gentoo = self.name == "gentoo"
is_windows = WINDOWS.get(self.name, None)
if is_fedora:
self.fullname = "fedora"
self.version = is_fedora.group(1)
Expand Down Expand Up @@ -101,6 +107,10 @@
self.version = "rolling"
self.tag = "gentoo"
self.type = "gentoo"
elif is_windows:
self.fullname, self.version = WINDOWS[self.name]
self.tag = "windows"
self.type = "windows"

Check warning on line 113 in qubesbuilder/distribution.py

View check run for this annotation

Codecov / codecov/patch

qubesbuilder/distribution.py#L111-L113

Added lines #L111 - L113 were not covered by tests
else:
raise DistributionError(
f"Unsupported distribution '{self.distribution}'."
Expand Down Expand Up @@ -140,3 +150,8 @@

def is_gentoo(self) -> bool:
return self.name == "gentoo"

def is_windows(self) -> bool:
if WINDOWS.get(self.name, None):
return True

Check warning on line 156 in qubesbuilder/distribution.py

View check run for this annotation

Codecov / codecov/patch

qubesbuilder/distribution.py#L156

Added line #L156 was not covered by tests
return False
Loading