To reduce 90% of common post here i decided to learn to use salt on Qubes to provide pre-configured hardened template and browsers. I spend 3-4 days working on this.

What is salt ? Read here https://doc.qubes-os.org/en/latest/user/advanced-topics/salt.html but with simple term you can say it's a "advanced bash script" to setup your own config under Qubes

Once you have installed everything i recommend you to switch to this guide https://forum.qubes-os.org/t/qubes-os-live-mode-dom0-in-ram-non-persistent-boot-ram-wipe-protection-against-forensics-tails-mode-hardening-dom0-root-read-only-paranoid-security/38868

The public target

The public target of this guide is advanced Qubes users (for now) i will provide the same setup for noob users soon

Template needed

The guide is using debian-13-minimal and fedora-43-minimal template so you will need to install those templates before starting

How to install ?

The debian/kicksecure salt file require sys-whonix to setup the template but the fedora salt file do not require sys-whonix

To use the "automatic installation" file you need to move the file inside dom0 with this command

qvm-run --pass-io <src-vm> 'cat /path/to/file_in_src_domain' > /path/to/file_name_in_dom0

To be more clear the command must look like this

qvm-run --pass-io fedora-43 'cat /home/user/Downlods/firefox.sls' > firefox.sls

Then do sudo mv (name of the file) /srv/salt/

or you can use this guide to copy paste easily

The file must be put at /srv/salt/(name of the file).sls

The name of the file doesn't matter you can name the file ex.sls it will work

In dom0 open a terminal and do sudo qubesctl state.apply librewolf (don't put "sls" at the end of the command) sudo qubesctl state.apply x where x = the name of the file in /srv/salt/

If you do not have enough ram the installation process will abort so don't open too much vm at the same time.

Features of the installation

Features provided by the installation :

✅ Deny read access to root file system , home directories except Downloads and machine-id for browsers using my apparmor profiles ✅ Dark theme enabled by default ✅ Anonymize hostname at boot for Template, Appvm, Dispvm (works only for deb/kicksecure template for now.. the script doesn't work in fedora template i don't know why..) ✅ Every installation of browsers will be done by using apt-transport-tor (for deb based template) because fedora do not provide this feature ✅ Firefox is using arkenfox user.js and brave is hardened by default the browser profile is coming from here no telemetry , no ai enabled etc... ✅ qvm-firewall enabled by default for the Appvm we allow only dns traffic , https , http trafic everything else will be drop ✅ Minimize debian even more thanks to qubist post ✅ Automatically install Ublock origin when firefox start (like librewolf) ✅ Automatically setup wget proxy and curl proxy to install software in the template (works only in debian for unknow reason) ✅ Apparmor enabled by default the browser , Nautilus and qubes features will use my apparmor profiles

[details="debian-brave-apparmor.sls"]

start-debian-minimal:
  qvm.start:
    - name: debian-13-minimal

update-debian-minimal:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c 'apt-get update && apt-get -y full-upgrade'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: start-debian-minimal

shutdown-debian-minimal:
  qvm.shutdown:
    - name: debian-13-minimal
    - require:
      - qvm: update-debian-minimal

setup-transport:
  qvm.start:
    - name: debian-13-minimal
    - require:
      - qvm: shutdown-debian-minimal    

fix-locale:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c "sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && /usr/sbin/locale-gen" && \
        sed -i 's/^# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
        /usr/sbin/locale-gen
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-transport


setup-tor:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c 'apt-get -y install apt-transport-tor alacritty'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: fix-locale

minimize-debian:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c 'apt-get -y purge \
        apt-transport-https \
        apt-utils \
        cpio \
        cron \
        cron-daemon-common \
        debconf-i18n \
        dhcpcd-base \
        eatmydata \
        fdisk \
        ifupdown \
        iproute2 \
        iputils-ping \
        less \
        libcap2-bin \
        libeatmydata1 \
        libfdisk1 \
        libgcrypt20 \
        libidn2-0 \
        libjansson4 \
        libk5crypto3 \
        libkeyutils1 \
        libkrb5support0 \
        libmnl0 \
        libnewt0.52 \
        libp11-kit0 \
        libsemanage-common \
        libsepol2 \
        libslang2 \
        libtext-iconv-perl \
        libtirpc-common \
        libxtables12 \
        logrotate \
        nftables \
        tasksel \
        whiptail \
        xterm && apt-get -y autoremove'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-tor

shutdown-after-tor:
  qvm.shutdown:
    - name: debian-13-minimal
    - require:
      - qvm: minimize-debian


clone-base-apparmor:
  qvm.clone:
    - name: base-apparmor
    - source: debian-13-minimal
    - require:
      - qvm: shutdown-after-tor

config-apt:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/sources.list
        deb tor+https://deb.debian.org/debian trixie main contrib non-free-firmware
        deb tor+https://deb.debian.org/debian-security trixie-security main contrib non-free-firmware
        deb tor+https://deb.debian.org/debian trixie-backports main
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: clone-base-apparmor


enable-dark-theme:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/environment
         GTK_THEME=Adwaita:dark
         QT_QPA_PLATFORMTHEME=qt5ct
         QT_STYLE_OVERRIDE=Adwaita-dark               
        EOF' 
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: config-apt

random-hostname:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat > /etc/rc.local << '"'"'EOF'"'"'
        #!/bin/bash
        # Anonymize hostname with random name at boot

        RANDOM_HOSTNAME=$(cat /dev/urandom | tr -dc "a-z" | head -c 12)
        echo "$RANDOM_HOSTNAME" > /etc/hostname
        hostname "$RANDOM_HOSTNAME"
        sed -i "s/^127\.0\.0\.1.*/127.0.0.1 $RANDOM_HOSTNAME localhost/" /etc/hosts
        hostnamectl set-hostname "$RANDOM_HOSTNAME" 2>/dev/null || true
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: enable-dark-theme


random-hostname-exec:
  qvm.run:
    - name: base-apparmor
    - cmd: chmod +x /etc/rc.local
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: random-hostname


config-apt-onion-qubes:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/sources.list.d/qubes-r4.list 
        deb [arch=amd64 signed-by=/usr/share/keyrings/qubes-archive-keyring-4.3.gpg] tor+http://deb.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r4.3/vm trixie main
        # Backup HTTPS repository (commented)
        #deb [arch=amd64 signed-by=/usr/share/keyrings/qubes-archive-keyring-4.3.gpg ] https://deb.qubes-os.org/r4.3/vm trixie main"
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: random-hostname-exec


set-pinning-backports:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/preferences.d/backports
        Package: *
        Pin: release n=trixie-backports
        Pin-Priority: 900
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: config-apt-onion-qubes

set-pinning-stable:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/preferences.d/stable
        Package: *
        Pin: release n=stable
        Pin-Priority: 500
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: set-pinning-backports

update-base-apparmor:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt-get update && apt-get -y full-upgrade'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: set-pinning-backports
      - qvm: set-pinning-stable

restart:
  qvm.shutdown:
    - name: base-apparmor
    - require:
      - qvm: update-base-apparmor

start-again:
  qvm.start:
    - name: base-apparmor
    - require:
      - qvm: restart

add-unstable-repo:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >>/etc/apt/sources.list
        deb tor+https://deb.debian.org/debian/ unstable main
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: start-again

create-apparmor-pinning:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/preferences.d/unstable-pin
        Package: apparm* python*-appar* python*-libappar*
        Pin: release a=unstable
        Pin-Priority: 990
        Package: *
        Pin: release a=unstable
        Pin-Priority: 1
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: add-unstable-repo

update-indexes-after-unstable:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt update'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: create-apparmor-pinning

install-apparmor:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt install -y -t unstable apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: update-indexes-after-unstable

shutdown-before-kernelopts:
  qvm.shutdown:
    - name: base-apparmor
    - require:
      - qvm: install-apparmor

set-apparmor-kernelopts:
  qvm.prefs:
    - name: base-apparmor
    - kernelopts: 'swiotlb=2048 apparmor=1 security=apparmor'
    - require:
      - qvm: shutdown-before-kernelopts

restart-base-apparmor:
  qvm.start:
    - name: base-apparmor
    - require:
      - qvm: set-apparmor-kernelopts

minimize-git:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt-get install --no-install-recommends -y git'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: restart-base-apparmor

speed-boost-apparmor:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c '
        tee -a /etc/apparmor/parser.conf > /dev/null <<EOF
        write-cache
        cache-loc /etc/apparmor/earlypolicy/
        Optimize=compress-fast
        EOF
        '
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: minimize-git

shutdown-base-apparmor-before-clone:
  qvm.shutdown:
    - name: base-apparmor
    - require:
      - qvm: speed-boost-apparmor


cloning-apparmor:
  qvm.clone:
    - name: debian-apparmor-brave
    - source: base-apparmor
    - flags:
      - shutdown

start-brave-template:
  qvm.start:
    - name: debian-apparmor-brave
    - require:
      - qvm: cloning-apparmor

# we install the tor package you will need it to install package in appvm or dispvm 
install-curl-tor:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: 'apt-get -y install tor curl'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: start-brave-template

# by default the tor package will enable the tor service but we are already using sys-whonix it's useless
disable-tor-service:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: 'systemctl disable tor'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: install-curl-tor

download-brave-key:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        bash -c "export all_proxy=http://127.0.0.1:8082/ && \
        curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg \
        https://brave-browser-apt-release.s3.brave4u7jddbv7cyviptqjc7jusxh72uik7zt6adtckl5f4nwy2v72qd.onion/brave-browser-archive-keyring.gpg"
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: disable-tor-service

add-brave-repo:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        bash -c "export all_proxy=http://127.0.0.1:8082/ && \
        curl -fsSLo /etc/apt/sources.list.d/brave-browser-release.sources \
        https://brave-browser-apt-release.s3.brave4u7jddbv7cyviptqjc7jusxh72uik7zt6adtckl5f4nwy2v72qd.onion/brave-browser.sources"
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: download-brave-key


force-brave-onion-apt:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: | 
        /bin/bash -c 'cat > /etc/apt/sources.list.d/brave-browser-release.sources << EOF
        Types: deb
        URIs: https://brave-browser-apt-release.s3.brave4u7jddbv7cyviptqjc7jusxh72uik7zt6adtckl5f4nwy2v72qd.onion
        Suites: stable
        Components: main
        Architectures: amd64 arm64
        Signed-By: /usr/share/keyrings/brave-browser-archive-keyring.gpg
        EOF'
    - user: root
    - flags:
      - pass-io
      - nogui
    - require:
      - qvm: add-brave-repo

install-brave:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        bash -c 'apt-get update && apt-get install -y brave-browser zenity ffmpeg libavcodec-extra pipewire-qubes qubes-core-agent-networking nautilus qubes-core-agent-nautilus && \ 
        apt-get -y purge gnome-keyring && \
        apt-get -y autoremove' 
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: force-brave-onion-apt


setup-apparmor-profiles-dkzkz:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        /bin/bash -c 'export all_proxy=http://127.0.0.1:8082/ && \
        cd /tmp && \
        git clone https://codeberg.org/dkzkz/apparmor-qubes && \
        cd /tmp/apparmor-qubes/stable/browser/brave && \ 
        mv bra* /etc/apparmor.d/ && \ 
        mv /tmp/apparmor-qubes/stable/file-manager/nautilus /etc/apparmor.d/ && \ 
        mv /tmp/apparmor-qubes/stable/display-vm/Xorg /etc/apparmor.d/Xorg && \ 
        cd /tmp/apparmor-qubes/stable/qubes-scripts/ && \ 
        mv q* /etc/apparmor.d/ && \
        cd /tmp/apparmor-qubes/stable/xdg-open/ && \ 
        mv open /etc/apparmor.d/'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: install-brave

enable-brave-apparmor:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        /bin/bash -c 'apparmor_parser -r /etc/apparmor.d/brav* && \
        apparmor_parser -r /etc/apparmor.d/Xorg && \
        apparmor_parser -r /etc/apparmor.d/qubes* && \
        apparmor_parser -r /etc/apparmor.d/nautilus && \ 
        apparmor_parser -r /etc/apparmor.d/open'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-apparmor-profiles-dkzkz

cleanup-apparmor-repo:
  qvm.run:
    - name: debian-apparmor-brave 
    - cmd: 'rm -rf /tmp/apparmor-qubes'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: enable-brave-apparmor

create-brave-dir:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: 'mkdir -p /opt/brave.com/brave'
    - user: root
    - flags:
      - nogui       
      - pass-io
    - require:
      - qvm: cleanup-apparmor-repo

setup-proxy-wget:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        /bin/bash -c '
        echo "proxy = http://127.0.0.1:8082" >> ~/.curlrc
        echo "use_proxy = on" >> ~/.wgetrc
        echo "http_proxy = 127.0.0.1:8082" >> ~/.wgetrc
        echo "https_proxy = 127.0.0.1:8082" >> ~/.wgetrc'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: create-brave-dir

write-initial-preferences:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        /bin/bash -c 'cd /tmp && \
        wget https://codeberg.org/dkzkz/apparmor-qubes/raw/branch/main/install/salt/debian/apparmor/brave-browser/brave-browser.sls/initial_preferences && \
        mv ini* /opt/brave.com/brave/' 
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-proxy-wget

create-policy-dir:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: 'mkdir -p /etc/brave/policies/managed'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: write-initial-preferences

write-group-policy:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/brave/policies/managed/GroupPolicy.json
        {
          "MetricsReportingEnabled": false
        }
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: create-policy-dir

make-skel-dir:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: 'mkdir -p /etc/skel/.config/BraveSoftware/Brave-Browser'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: write-group-policy

write-skel-local-state:
  qvm.run:
    - name: debian-apparmor-brave
    - cmd: |
        /bin/bash -c 'cat <<EOF | sudo tee /etc/skel/.config/BraveSoftware/Brave-Browser/Local\ State > /dev/null
        {
        "background_mode": {"enabled": false},
        "hardware_acceleration_mode": {"enabled": false},
        "brave": {
        "p3a": {"enabled": false},
        "stats": {"reporting_enabled": false}
        },
        "default_apps": {"extensions": {}},
        "extensions": {
        "theme": {"colors": {}}
        },
        "sync_promo": {"user_skipped": true}
        }
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: make-skel-dir

shutdown-template:
  qvm.shutdown:
    - name: debian-apparmor-brave
    - require:
      - qvm: write-skel-local-state

set-template-colour:
  qvm.prefs:
    - name: debian-apparmor-brave
    - label: black
    - require:
      - qvm: shutdown-template

create-dvm:
  qvm.present:
    - name: brave-browser-dvm
    - template: debian-apparmor-brave
    - label: yellow
    - require:
      - qvm: set-template-colour

config-firewall:
  cmd.run:
    - name: |
        printf "action=accept specialtarget=dns
        action=accept proto=tcp dstports=443
        action=accept proto=tcp dstports=80
        action=accept proto=tcp dstports=9050
        action=drop" | qubesd-query dom0 admin.vm.firewall.Set brave-browser-dvm

config-brave-dvm:
  qvm.prefs:
    - name: brave-browser-dvm
    - memory: 300
    - maxmem: 3000
    - template-for-dispvms: true
    - require:
      - qvm: create-dvm

enable-brave-appmenus-dispvm:
  qvm.features:
    - name: brave-browser-dvm
    - enable:
      - service.apparmor
      - appmenus-dispvm
    - require:
      - qvm: config-brave-dvm

hardening-dvm:
  qvm.features:
    - name: brave-browser-dvm
    - set:
      - anon-timezone: '1'
      - menu-items: brave-browser.desktop Alacritty.desktop 
    - require:
      - qvm: enable-brave-appmenus-dispvm

[/details]

[details="debian-mullvad-apparmor.sls"]

start-debian-minimal: 
  qvm.start:
    - name: debian-13-minimal

update-debian-minimal:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c 'apt-get update && apt-get -y full-upgrade'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: start-debian-minimal

shutdown-debian-minimal:
  qvm.shutdown:
    - name: debian-13-minimal
    - require:
      - qvm: update-debian-minimal

setup-transport:
  qvm.start:
    - name: debian-13-minimal
    - require:
      - qvm: shutdown-debian-minimal    

fix-locale:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c "sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && /usr/sbin/locale-gen" && \
        sed -i 's/^# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
        /usr/sbin/locale-gen
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-transport


setup-tor:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c 'apt-get -y install apt-transport-tor alacritty'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: fix-locale

minimize-debian:
  qvm.run:
    - name: debian-13-minimal
    - cmd: |
        /bin/bash -c 'apt-get -y purge \
        apt-transport-https \
        apt-utils \
        cpio \
        cron \
        cron-daemon-common \
        debconf-i18n \
        dhcpcd-base \
        eatmydata \
        fdisk \
        ifupdown \
        iproute2 \
        iputils-ping \
        less \
        libcap2-bin \
        libeatmydata1 \
        libfdisk1 \
        libgcrypt20 \
        libidn2-0 \
        libjansson4 \
        libk5crypto3 \
        libkeyutils1 \
        libkrb5support0 \
        libmnl0 \
        libnewt0.52 \
        libp11-kit0 \
        libsemanage-common \
        libsepol2 \
        libslang2 \
        libtext-iconv-perl \
        libtirpc-common \
        libxtables12 \
        logrotate \
        nftables \
        tasksel \
        whiptail \
        xterm && apt-get -y autoremove'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-tor

shutdown-after-tor:
  qvm.shutdown:
    - name: debian-13-minimal
    - require:
      - qvm: minimize-debian

clone-base-apparmor:
  qvm.clone:
    - name: base-apparmor
    - source: debian-13-minimal
    - require:
      - qvm: shutdown-after-tor

config-apt:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/sources.list
        deb tor+https://deb.debian.org/debian trixie main contrib non-free-firmware
        deb tor+https://deb.debian.org/debian-security trixie-security main contrib non-free-firmware
        deb tor+https://deb.debian.org/debian trixie-backports main
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: clone-base-apparmor

enable-dark-theme:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/environment
         GTK_THEME=Adwaita:dark
         QT_QPA_PLATFORMTHEME=qt5ct
         QT_STYLE_OVERRIDE=Adwaita-dark               
        EOF' 
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: config-apt

random-hostname:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat > /etc/rc.local << '"'"'EOF'"'"'
        #!/bin/bash
        # Anonymize hostname with random name at boot

        RANDOM_HOSTNAME=$(cat /dev/urandom | tr -dc "a-z" | head -c 12)
        echo "$RANDOM_HOSTNAME" > /etc/hostname
        hostname "$RANDOM_HOSTNAME"
        sed -i "s/^127\.0\.0\.1.*/127.0.0.1 $RANDOM_HOSTNAME localhost/" /etc/hosts
        hostnamectl set-hostname "$RANDOM_HOSTNAME" 2>/dev/null || true
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: enable-dark-theme


random-hostname-exec:
  qvm.run:
    - name: base-apparmor
    - cmd: chmod +x /etc/rc.local
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: random-hostname


config-apt-onion-qubes:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/sources.list.d/qubes-r4.list 
        deb [arch=amd64 signed-by=/usr/share/keyrings/qubes-archive-keyring-4.3.gpg] tor+http://deb.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r4.3/vm trixie main
        # Backup HTTPS repository (commented)
        #deb [arch=amd64 signed-by=/usr/share/keyrings/qubes-archive-keyring-4.3.gpg ] https://deb.qubes-os.org/r4.3/vm trixie main"
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: random-hostname-exec


set-pinning-backports:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/preferences.d/backports
        Package: *
        Pin: release n=trixie-backports
        Pin-Priority: 900
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: config-apt-onion-qubes

set-pinning-stable:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/preferences.d/stable
        Package: *
        Pin: release n=stable
        Pin-Priority: 500
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: set-pinning-backports

update-base-apparmor:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt-get update && apt-get -y full-upgrade'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: set-pinning-backports
      - qvm: set-pinning-stable

restart:
  qvm.shutdown:
    - name: base-apparmor
    - require:
      - qvm: update-base-apparmor

start-again:
  qvm.start:
    - name: base-apparmor
    - require:
      - qvm: restart

add-unstable-repo:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >>/etc/apt/sources.list
        deb tor+https://deb.debian.org/debian/ unstable main
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: start-again

create-apparmor-pinning:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'cat <<EOF >/etc/apt/preferences.d/unstable-pin
        Package: apparm* python*-appar* python*-libappar*
        Pin: release a=unstable
        Pin-Priority: 990
        Package: *
        Pin: release a=unstable
        Pin-Priority: 1
        EOF'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: add-unstable-repo

update-indexes-after-unstable:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt update'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: create-apparmor-pinning

install-apparmor:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt install -y -t unstable apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: update-indexes-after-unstable

shutdown-before-kernelopts:
  qvm.shutdown:
    - name: base-apparmor
    - require:
      - qvm: install-apparmor

set-apparmor-kernelopts:
  qvm.prefs:
    - name: base-apparmor
    - kernelopts: 'swiotlb=2048 apparmor=1 security=apparmor'
    - require:
      - qvm: shutdown-before-kernelopts

restart-base-apparmor:
  qvm.start:
    - name: base-apparmor
    - require:
      - qvm: set-apparmor-kernelopts

minimize-git:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c 'apt-get install --no-install-recommends -y git'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: restart-base-apparmor

speed-boost-apparmor:
  qvm.run:
    - name: base-apparmor
    - cmd: |
        /bin/bash -c '
        tee -a /etc/apparmor/parser.conf > /dev/null <<EOF
        write-cache
        cache-loc /etc/apparmor/earlypolicy/
        Optimize=compress-fast
        EOF
        '
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: minimize-git

shutdown-base-apparmor-before-clone:
  qvm.shutdown:
    - name: base-apparmor
    - require:
      - qvm: speed-boost-apparmor


clone-debian-13-minimal-to-mullvad-template:
  qvm.clone:
    - name: debian-apparmor-mullvad
    - source: base-apparmor
    - flags:
      - shutdown
    - require:
      - qvm: shutdown-base-apparmor-before-clone


# Start the cloned template
start-mullvad-template:
  qvm.start:
    - name: debian-apparmor-mullvad
    - require:
      - qvm: clone-debian-13-minimal-to-mullvad-template


basic-tool-needed:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: 'apt-get -y install curl wget'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: start-mullvad-template 


setup-proxy-wget:
  qvm.run:
    - name: debian-apparmor-librewolf
    - cmd: |
        /bin/bash -c '
        echo "proxy = http://127.0.0.1:8082" >> ~/.curlrc
        echo "use_proxy = on" >> ~/.wgetrc
        echo "http_proxy = 127.0.0.1:8082" >> ~/.wgetrc
        echo "https_proxy = 127.0.0.1:8082" >> ~/.wgetrc'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: basic-tool-needed


# Download Mullvad signing key
download-mullvad-key:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: 'bash -c "export all_proxy=http://127.0.0.1:8082/ && curl -fsSLo /usr/share/keyrings/mullvad-keyring.asc https://repository.mullvad.net/deb/mullvad-keyring.asc"'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-proxy-wget

# Add Mullvad repository
add-mullvad-repository:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: |
        /bin/bash -c 'echo "deb [signed-by=/usr/share/keyrings/mullvad-keyring.asc arch=$( dpkg --print-architecture )] tor+https://repository.mullvad.net/deb/stable stable main" | tee /etc/apt/sources.list.d/mullvad.list'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: download-mullvad-key

# Update package indexes for Mullvad
update-for-mullvad:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: 'apt update'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: add-mullvad-repository

# Install Mullvad Browser, nautilus, and dependencies, then remove gnome-keyring
install-mullvad-browser-nautilus:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: |
        /bin/bash -c 'apt-get -y install mullvad-browser ffmpeg libavcodec-extra pipewire-qubes nautilus qubes-core-agent-networking zenity qubes-core-agent-nautilus && \
        apt-get -y purge gnome-keyring && \
        apt-get -y autoremove'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: update-for-mullvad


setup-apparmor-profiles-dkzkz:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: |
        /bin/bash -c 'export all_proxy=http://127.0.0.1:8082/ && \
        cd /tmp && \
        git clone https://codeberg.org/dkzkz/apparmor-qubes && \
        cd /tmp/apparmor-qubes/stable/browser/mullvad-browser && \ 
        mv mullv* /etc/apparmor.d/ && \ 
        mv /tmp/apparmor-qubes/stable/file-manager/nautilus /etc/apparmor.d/ && \ 
        mv /tmp/apparmor-qubes/stable/display-vm/Xorg /etc/apparmor.d/Xorg && \ 
        cd /tmp/apparmor-qubes/stable/qubes-scripts/ && \ 
        mv q* /etc/apparmor.d/ && \
        cd /tmp/apparmor-qubes/stable/xdg-open/ && \ 
        mv open /etc/apparmor.d/'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: install-mullvad-browser-nautilus

enable-mullvad-apparmor:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: |
        /bin/bash -c 'apparmor_parser -r /etc/apparmor.d/mullvad* && \
        apparmor_parser -r /etc/apparmor.d/Xorg && \
        apparmor_parser -r /etc/apparmor.d/qubes* && \
        apparmor_parser -r /etc/apparmor.d/nautilus && \ 
        apparmor_parser -r /etc/apparmor.d/open'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: setup-apparmor-profiles-dkzkz


remove-cloned-repository:
  qvm.run:
    - name: debian-apparmor-mullvad
    - cmd: 'rm -rf /tmp/apparmor-qubes'
    - user: root
    - flags:
      - nogui
      - pass-io
    - require:
      - qvm: enable-mullvad-apparmor

# Shutdown template after AppArmor configuration
shutdown-mullvad-template:
  qvm.shutdown:
    - name: debian-apparmor-mullvad
    - require:
      - qvm: remove-cloned-repository

# Set template color to black
set-template-color-black:
  qvm.prefs:
    - name: debian-apparmor-mullvad
    - label: black
    - require:
      - qvm: shutdown-mullvad-template

# Create AppVM from template as a disposable template
create-apparmor-mullvad-browser-dvm:
  qvm.present:
    - name: mullvad-browser-dvm
    - template: debian-apparmor-mullvad
    - label: yellow
    - require:
      - qvm: set-template-color-black


config-firewall:
  cmd.run:
    - name: |
        printf "action=accept specialtarget=dns
        action=accept proto=tcp dstports=443
        action=accept proto=tcp dstports=80
        action=drop" | qubesd-query dom0 admin.vm.firewall.Set mullvad-browser-dvm

# Set as disposable template
configure-mullvad-dvm:
  qvm.prefs:
    - name: mullvad-browser-dvm
    - template-for-dispvms: true
    - memory: 300
    - maxmem: 3000
    - require:
      - qvm: create-apparmor-mullvad-browser-dvm

# Enable appmenus-dispvm feature and set menu-items
enable-mullvad-appmenus-dispvm:
  qvm.features:
    - name: mullvad-browser-dvm
    - enable:
      - appmenus-dispvm
      - service.apparmor
    - require:
      - qvm: configure-mullvad-dvm

# Add mullvad-browser.desktop and xterm + hardening features to the menu
add-mullvad-browser-menu:
  qvm.features:
    - name: mullvad-browser-dvm
    - set:
      - anon-timezone: '1'
      - menu-items: mullvad-browser.desktop Alacritty.desktop 
    - require:
      - qvm: enable-mullvad-appmenus-dispvm
[/details]

I provide only two files because there is more than ~ 3 500 lines for all salt files and if i have to edit the post to update the content it would be painful for me almost impossible

To get the kicksecure version only brave for now go on my repo To get the debian version go on my codeberg repo To get the fedora version go on my codeberg repo

Technical details information

Every files have the same logic.

  1. Update the original template , shutdown the original template clone the template
  2. Debian and kicksecure files is using the onion repository of Qubes to update the system
  3. To allow users to use tor in brave-browser i added the tcp 9050 ports in the firewall for the brave file
  4. Debian is using the backports repository mixed with the stable repository to get recent packages and fix
  5. Debian andd kicksecure is using the unstable version of apparmor because of this issue
  6. The brave hardened value and arkenfox js profile is fetched from my codeberg repo i didn't touch anything to the original files
  7. Qubist provided a list of packages that can be removed in debian-13 i didn't removed nano , vim perl, gnupg
  8. IMCP request will not work due to the qvm-firewall rules i applied
  9. In debian template i removed xterm in favor of Alacritty terminal
  10. I probably forget some details but you can inspect the salt content if details are missing i will add them later
Know issue

Missing :

❌ A salt file to automatically update the apparmor profile from my repository to the template (i will do it soon) ❌ I didn't enable selinux for fedora to avoid any problem for users. Fedora-43 seems unstable for now nautilus doesn't launch properly. I replaced nautilus by thunar for fedora ❌ Hostname anonymize feature doesn't work on fedora i will need help for make the script work in fedora ❌ Librewolf is not in dark mode on fedora or debian i forget which one ❌ I'm not really sure if the qvm-features anon-timezone 1 is properly working in my testing i get the same issue as https://forum.qubes-os.org/t/anon-timezone-doesnt-work-for-me/39539 i enabled anon-timezone 1 for every salt file but my timezone didn't change when i tested on website like https://browserleaks.com/

If you want to contact me to improve the guide

Feel free to contact me on element @aatrfs76519:nope.chat for any suggestion about the guide

Thanks to

The curl and wget proxy The dark theme Brave browser hardened is coming from here To setup arkenfox js and ublock for firefox i used my own guide The qvm-firewall is coming from this discussion