Skip to content

Commit 0a55e26

Browse files
authored
Merge pull request #10 from sorenisanerd/dev
Merge dev branch
2 parents e8786bb + c04f0d0 commit 0a55e26

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+611
-159
lines changed

.github/workflows/build.yml

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ jobs:
1717
steps:
1818
- name: Install cosign
1919
uses: sigstore/[email protected]
20-
- name: Prevent man-db dpkg trigger (speeds up setup-mkosi)
21-
run: sudo rm /var/lib/man-db/auto-update
2220
- name: setup-mkosi
2321
uses: sorenisanerd/mkosi@main
2422
- name: Checkout code
@@ -65,6 +63,7 @@ jobs:
6563
- name: Run mkosi
6664
env:
6765
profiles: ${{ matrix.profiles }}
66+
MANGOS_GITHUB_URL: ${{ github.server_url }}/${{ github.repository }}
6867
run: mkosi --debug --profile="${profiles}"
6968
- name: List built artifacts
7069
run: find out/
@@ -76,7 +75,11 @@ jobs:
7675
set -x
7776
set -e
7877
79-
screen -d -m -S vm mkosi vm -- -uuid $(uuidgen)
78+
# mkosi doesn't pick this up from the tools dir for some reason
79+
sudo apt-get install -y ovmf
80+
81+
mkosi --debug vm -- -uuid $(uuidgen) > vm.log 2>&1 &
82+
pid=$!
8083
8184
success=0
8285
for try in {1..10}
@@ -88,14 +91,22 @@ jobs:
8891
fi
8992
sleep 5
9093
done
91-
test $success -eq 1
94+
95+
if [ $success -eq 1 ]
96+
then
97+
echo "mkosi ssh succeeded"
98+
else
99+
echo "mkosi ssh failed"
100+
cat vm.log
101+
exit 1
102+
fi
92103
mkosi ssh -- shutdown -h now || true
93104
sleep 5
94105
95106
- name: Remove symlinks
96107
run: find out/ -type l -delete
97108
- name: Compress artifacts
98-
run: for file in out/mangos_*.raw out/mangos_*.efi; do zstd --rm "$file" ; done
109+
run: for file in out/mangos{,-installer}_*.raw out/mangos_*.efi; do zstd --rm "$file" ; done
99110
- name: Sign artifacts
100111
run: for file in out/mangos* ; do cosign sign-blob -d -y --bundle "${file}.sigbundle" "${file}" > /dev/null; done
101112
- name: Upload build artifact (disk)
@@ -113,6 +124,16 @@ jobs:
113124
out/mangos_${{ env.IMAGE_VERSION }}.spdx.json
114125
out/mangos_${{ env.IMAGE_VERSION }}.syft.json
115126
name: mangos.${{ matrix.profiles }}.disk
127+
- name: Upload build artifact (installer)
128+
uses: actions/upload-artifact@v4
129+
with:
130+
path: |
131+
out/mangos-installer_${{ env.IMAGE_VERSION }}.raw.zst
132+
out/mangos-installer_${{ env.IMAGE_VERSION }}.cyclonedx.json
133+
out/mangos-installer_${{ env.IMAGE_VERSION }}.github.json
134+
out/mangos-installer_${{ env.IMAGE_VERSION }}.spdx.json
135+
out/mangos-installer_${{ env.IMAGE_VERSION }}.syft.json
136+
name: mangos.${{ matrix.profiles }}.installer
116137

117138
release:
118139
if: github.ref_type == 'tag'
@@ -131,7 +152,7 @@ jobs:
131152
mkdir release
132153
133154
mv artifacts/mangos.verity-full.disk/* release/
134-
155+
mv artifacts/mangos.verity-full.installer/* release/
135156
for f in artifacts/mangos.verity-full,docker.disk/* ; do
136157
mv "$f" "release/mangos+docker_$(basename $f | cut -f2- -d_)"
137158
done

.github/workflows/pr.yml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ jobs:
5858
set -x
5959
set -e
6060
61-
screen -d -m -S vm mkosi vm -- -uuid $(uuidgen)
61+
# mkosi doesn't pick this up from the tools dir for some reason
62+
sudo apt-get install -y ovmf
63+
64+
mkosi --debug vm -- -uuid $(uuidgen) > vm.log 2>&1 &
6265
6366
success=0
6467
for try in {1..10}
@@ -70,14 +73,22 @@ jobs:
7073
fi
7174
sleep 5
7275
done
73-
test $success -eq 1
76+
77+
if [ $success -eq 1 ]
78+
then
79+
echo "mkosi ssh succeeded"
80+
else
81+
echo "mkosi ssh failed"
82+
cat vm.log
83+
exit 1
84+
fi
7485
mkosi ssh -- shutdown -h now || true
7586
sleep 5
7687
7788
- name: Remove symlinks
7889
run: find out/ -type l -delete
7990
- name: Compress artifacts
80-
run: for file in out/mangos_*.raw out/mangos_*.efi; do zstd --rm "$file" ; done
91+
run: for file in out/mangos{,-installer}_*.raw out/mangos_*.efi ; do zstd --rm "$file" ; done
8192
- name: Upload build artifact (disk)
8293
id: upload-disk
8394
uses: actions/upload-artifact@v4
@@ -93,3 +104,13 @@ jobs:
93104
out/mangos_${{ env.IMAGE_VERSION }}.spdx.json
94105
out/mangos_${{ env.IMAGE_VERSION }}.syft.json
95106
name: mangos.${{ matrix.profiles }}.disk
107+
- name: Upload build artifact (installer)
108+
uses: actions/upload-artifact@v4
109+
with:
110+
path: |
111+
out/mangos-installer_${{ env.IMAGE_VERSION }}.raw.zst
112+
out/mangos-installer_${{ env.IMAGE_VERSION }}.cyclonedx.json
113+
out/mangos-installer_${{ env.IMAGE_VERSION }}.github.json
114+
out/mangos-installer_${{ env.IMAGE_VERSION }}.spdx.json
115+
out/mangos-installer_${{ env.IMAGE_VERSION }}.syft.json
116+
name: mangos.${{ matrix.profiles }}.installer

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
/mkosi.key
77
/build
88
/pkg
9+
/mkosi.packages
10+
/gpg

README.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,50 @@
44

55
If you look around, you can see that this is very much in its infancy, and not something you'd find running at Mastercard, so the name is more aspirational than anything.
66

7-
## What is it?
7+
## Installation instructions
8+
9+
The installer is provided as a disk image. Look for the `mangos-installer_x.y.z.raw` artifact. Write it to a USB stick and reboot. By default, it will use DHCP to configure the network. If you need to provide different network config, you can replace `ip=any` from the kernel command line by pressing `E` when the the boot prompt shows up.
10+
11+
Once the network is up, the installer enumerates the block devices and presents a screen to choose the target device. Once selected, the installer streams the appropriate release from Github and writes it directly to the selected block device and reboots. On my test rig, the whole thing takes less than 10 seconds. Very few guard rails and obviously destructive. Any existing data on the target device will be overwritten. Beware.
12+
13+
## First boot process
14+
15+
When mangos boots, it ensures all the right partitions have been created. The disk images we distribute only contain a subset:
16+
17+
* An ESP (EFI System Partition),
18+
* a root partition,
19+
* a verity hash partition, and
20+
* a signature partition.
21+
22+
On first boot, the rest are created:
23+
24+
* An alternate root partition,
25+
* an alternate verity hash partition,
26+
* an alternate signature partition,
27+
* a swap partition,
28+
* `/var/tmp`, and
29+
* `/var`.
30+
31+
See the [Updates](#updates) section for more details on the alternate partitions.
32+
33+
The last three are encrypted using a TPM backed key. The key is bound directly to PCR 7 (`--tpm2-pcrs=7`) and indirectly to PCR 11 (`--tpm2-public-key-pcrs=11`). The signatures for the expected values of PCR 11 are embedded in the UKI (Unified Kernel Image). This allows unlocking when booting any UKI signed with the same key, provided PCR 7 has not been changed. PCR 7 records the Secure Boot policy, so disabling Secure Boot, adding/removing keys in the firmware, etc. will all prevent accessing the keys.
34+
35+
## Updates
36+
37+
1. When a new version of Mangos is released, it is made available as a disk image for new installs, and split into partition files for updates.
38+
1. `systemd-sysupdate` periodically checks for updates. However, it expects to find updates in a flat directory structure. A small, local proxy (`mangos-sd-gh-proxy`) performs the necessary translation.
39+
1. When an update is found, it is written to the inactive partition set.
40+
1. On reboot, the bootloader will attempt to boot into the newest version and fall back to the older one if it fails.
41+
42+
## What is MANGOS anyway?
843

944
With MANGOS, we want to build a highly secure operating system beyond what you can achieve with garden variety Linux distros.
1045

1146
### Disk layout
1247

1348
First, MANGOS gets installed as an image. The filesystem is [erofs](https://docs.kernel.org/filesystems/erofs.html), so the filesystem driver doesn't even implement any write operations. The image also contains a [Verity](https://docs.kernel.org/admin-guide/device-mapper/verity.html) hash partition. The kernel uses this to verify that the erofs filesystem hasn't been tampered with. A third partition contains a signature for the hash partition. The kernel will check this signature against a certificate that is embedded in the kernel at build time. Also, the kernel itself is signed, and systems are configured with Secure Boot to only allow kernels (or boot managers/loaders) signed by this key. Finally, the system will refuse to start if Secure Boot is disabled.
14-
15-
System updates are also provided as images. As they are made available, they are downloaded and written to a second set of 3 partitions as (data + hash + signature). On reboot, the bootloader will attempt to boot into the newest version and fall back to the older one if it fails.
16-
1749
The filesystem in the image is the root filesystem, so even `/etc` is read-only. Configuration is done using [configuration extensions](https://www.freedesktop.org/software/systemd/man/latest/systemd-sysext.html). These are also signed and verified against the certificate in the kernel.
1850

19-
On first boot, `systemd-repart` creates 3 partitions: swap, `/var`, and `/var/tmp`. They are encrypted using a key rooted in the TPM, so can't be decrypted anywhere else.
20-
2151
If you've ever used CoreOS, a lot of these concepts will seem familiar. If you're an Android user, you're using a lot of this already!
2252

2353
### Runtime environment and compatibility

docs/keys.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Encryption and signing key types and sizes
2+
3+
Different parts of the system need different key types and sizes, and the tooling doesn't alwways prevent you from making the wrong choices.
4+
5+
## Kernel signing key
6+
7+
| Type | Size | Extra info | Working? |
8+
|------|-----------|-------------------------------|----------|
9+
| RSA | 4096 bits | Extensions as below | Yes |
10+
| RSA | 2048 bits | As produced by `mkosi genkey` | No |
11+
12+
The kernel's build system creates a signing key using [this config](https://github.com/torvalds/linux/blob/8f5ae30d69d7543eee0d70083daf4de8fe15d585/certs/default_x509.genkey) (shown here with irrelevant parts removed):
13+
14+
```
15+
[ req ]
16+
default_bits = 4096
17+
x509_extensions = myexts
18+
19+
[ myexts ]
20+
basicConstraints=critical,CA:FALSE
21+
keyUsage=digitalSignature
22+
subjectKeyIdentifier=hash
23+
authorityKeyIdentifier=keyid
24+
```
25+
26+
I'm not sure if the extensions must be set up exactly like this.
27+
28+
## Verity signing key
29+
30+
| Type | Size | Extra info | Working? |
31+
|------|-----------|-------------------------------|----------|
32+
| RSA | 4096 bits | Extensions as for kernel | Yes |
33+
| RSA | 2048 bits | As produced by `mkosi genkey` | Yes |
34+
35+
36+
## Expected PCR signatures
37+
38+
| Type | Size | Working? |
39+
|------|-----------|----------|
40+
| RSA | 4096 bits | No |
41+
| RSA | 2048 bits | Yes |
42+
43+
When creating the encrypted, local storage, a key is loaded into the TPM along with the conditions required to get access to the key.
44+
Validating those conditions involves loading a public key into the TPM to verify a signature.
45+
It may depend on your specific TPM, but with 4096 bit keys, I'd get errors.
46+
With a 2048 bit key, everything was fine.
47+
48+
mkosi uses `SignExpectedPCRKey` to sign the expected PCR values that are embedded in the UKI.
49+
If one is not provided, it will use `mkosi.key` if it exists.
50+
51+
## Secure boot key
52+
53+
(Add info)
54+
55+
## Repository signing key (sysupdate)
56+
57+
`SHA256SUMS` needs to be signed by a GPG key.
58+
Not sure if there are any special requirements for the key.

mangos-sd-gh-proxy.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python3
2+
import re
3+
from flask import Flask, redirect
4+
from github import Github, Auth
5+
import github
6+
7+
sha256sums = ''
8+
9+
def os_release_to_dict(fname='/usr/lib/os-release'):
10+
result = {}
11+
with open(fname, 'r') as fp:
12+
for l in fp:
13+
if '=' in l:
14+
key, value = l.split('=', 1)
15+
result[key.strip()] = value.strip().strip('"')
16+
return result
17+
18+
os_release = os_release_to_dict()
19+
if 'MANGOS_GITHUB_URL' in os_release:
20+
github_url = os_release['MANGOS_GITHUB_URL']
21+
repo_full = '/'.join(github_url.split('/')[-2:])
22+
23+
app = Flask(__name__)
24+
25+
def get_github_client() -> Github:
26+
"""
27+
Initialize and return a GitHub client.
28+
Replace 'your_token_here' with your actual GitHub token.
29+
"""
30+
return Github()
31+
32+
@app.route('/SHA256SUMS')
33+
def sha256_sums() -> str:
34+
global sha256sums
35+
if sha256sums:
36+
return sha256sums
37+
38+
g = get_github_client()
39+
repo = g.get_repo(repo_full, lazy=True)
40+
releases = repo.get_releases()
41+
for release in releases:
42+
for asset in release.raw_data['assets']:
43+
digest = asset["digest"]
44+
name = asset["name"]
45+
if digest.startswith("sha256:"):
46+
sha256sums += f'{digest[7:]} *{name}\n'
47+
48+
return sha256sums
49+
50+
@app.route('/<path:filename>')
51+
def file_content(filename: str) -> str:
52+
version = filename.split('_')[1]
53+
for ext in ['.gz', '.zst']:
54+
if version.endswith(ext):
55+
version = version[:-len(ext)]
56+
break
57+
58+
for ext in ['.efi', '.cyclonedx.json', '.github.json', '.raw', '.spdx.json', '.syft.json']:
59+
if version.endswith(ext):
60+
version = version[:-len(ext)]
61+
break
62+
63+
version = re.sub(r'\.root-x86-64(-verity(-sig)?)?\.[a-z0-9]{32}$', '', version)
64+
65+
return redirect(f'{github_url}/releases/download/v{version}/filename')
66+
67+
def main():
68+
app.run(host='0.0.0.0', port=1002)
69+
70+
if __name__ == "__main__":
71+
main()

mkosi.conf

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Release=oracular
99
# Generated using `openssl passwd -6`
1010
RootPassword=hashed:$6$OM2raXimIv8ucv8M$KwmcGa3kMjKlxvvpVdTVZ9khXy5pp2FJU0gNWqJ6nEMG7Yut2sxILhr7bs/yEC.WoUYP.hlBWt0zn4W7XCcyi.
1111

12-
KernelCommandLine=systemd.machine_id=firmware
12+
KernelCommandLine=systemd.machine_id=firmware lsm=lockdown,capability,landlock,yama,apparmor,ima,evm,bpf
1313

1414
# System TZ should always be UTC. Users can set their own, if they want.
1515
Timezone=UTC
@@ -75,13 +75,17 @@ Packages=
7575
gdisk
7676
pciutils
7777
lshw
78+
python3-flask
79+
python3-github
7880

7981
RemoveFiles=
8082
/usr/share/locale/*
8183

8284
ExtraTrees=sshd-keygen:/usr/lib/sshd-keygen
8385
[email protected]:/usr/lib/systemd/system/
8486
sshd-keygen.target:/usr/lib/systemd/system/
87+
resources/sysupdate.d:/usr/lib/
88+
mangos-sd-gh-proxy.py:/usr/bin/mangos-sd-gh-proxy
8589

8690
[Build]
8791
BuildDirectory=%D/build
@@ -91,9 +95,11 @@ History=yes
9195
# Reuses partial builds, so also improves cache use.
9296
Incremental=yes
9397

98+
Environment=MANGOS_GITHUB_URL
99+
94100
[Config]
95101
#Dependencies=initrd,admin-toolbox
96-
Profiles=verity-full
102+
Profiles=verity-full,secureboot
97103

98104
[Include]
99105
Include=%D/resources/common-kernel
@@ -132,7 +138,7 @@ KVM=yes
132138
RuntimeNetwork=user
133139
RuntimeSize=20G
134140
Firmware=uefi
135-
TPM=False
141+
TPM=yes
136142
Ephemeral=yes
137143
RAM=4G
138144
CPUs=2

mkosi.extra/usr/lib/repart.d/30-swap.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[Partition]
22
Type=swap
33
Format=swap
4-
#Encrypt=tpm2
4+
Encrypt=tpm2
55
SizeMinBytes=4G
66
SizeMaxBytes=4G
77
FactoryReset=on
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
[Partition]
22
Type=tmp
33
Format=xfs
4+
Encrypt=tpm2
45
SizeMinBytes=5G
5-
# Without a TPM, this fails.
6-
#Encrypt=tpm2
76
FactoryReset=on

0 commit comments

Comments
 (0)