├── etc ├── qubes-updates-cache │ ├── urls │ │ ├── 20-https.bash │ │ ├── 10-onion.bash.EXAMPLE │ │ └── 30-default-rules.bash │ └── squid.conf └── systemd │ └── system │ └── multi-user.target.wants │ └── qubes-updates-cache.service ├── usr ├── lib │ ├── systemd │ │ └── system │ │ │ ├── qubes-updates-proxy-forwarder.socket.d │ │ │ └── qubes-updates-cache.conf │ │ │ └── qubes-updates-cache.service │ └── qubes │ │ └── updates-cache-urls └── share │ └── qubes-updates-cache │ └── errors │ └── ERR_INVALID_URL ├── install └── README /etc/qubes-updates-cache/urls/20-https.bash: -------------------------------------------------------------------------------- 1 | upgrades+=( https ) 2 | -------------------------------------------------------------------------------- /etc/qubes-updates-cache/urls/10-onion.bash.EXAMPLE: -------------------------------------------------------------------------------- 1 | upgrades+=( onion ) 2 | -------------------------------------------------------------------------------- /etc/systemd/system/multi-user.target.wants/qubes-updates-cache.service: -------------------------------------------------------------------------------- 1 | /usr/lib/systemd/system/qubes-updates-cache.service -------------------------------------------------------------------------------- /usr/lib/systemd/system/qubes-updates-proxy-forwarder.socket.d/qubes-updates-cache.conf: -------------------------------------------------------------------------------- 1 | [Unit] 2 | ConditionPathExists=!/var/run/qubes-service/qubes-updates-cache 3 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | 4 | id=vm-updates 5 | 6 | getent passwd $id >/dev/null || 7 | useradd --shell /bin/false --home / --system --user-group $id 8 | 9 | install -d -m 750 -o $id -g $id "$DESTDIR"/var/lib/qubes/$id 10 | umask 022 11 | cp -R etc usr "$DESTDIR"/ 12 | -------------------------------------------------------------------------------- /usr/share/qubes-updates-cache/errors/ERR_INVALID_URL: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |Invalid URL requested from qubes-updates-cache:
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /usr/lib/systemd/system/qubes-updates-cache.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Qubes updates cache (Squid) 3 | ConditionPathExists= /var/run/qubes-service/qubes-updates-cache 4 | ConditionPathExists=!/var/run/qubes-service/qubes-updates-proxy 5 | 6 | [Service] 7 | LimitNOFILE=16384 8 | Environment=SQUID_CONF=/etc/qubes-updates-cache/squid.conf 9 | ExecStartPre=/usr/sbin/squid -f $SQUID_CONF -NzF 10 | ExecStart =/usr/sbin/squid -f $SQUID_CONF -N 11 | ExecReload =/bin/kill -HUP $MAINPID 12 | KillMode=process 13 | TimeoutSec=0 14 | Restart=on-failure 15 | RestartSec=1s 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /etc/qubes-updates-cache/squid.conf: -------------------------------------------------------------------------------- 1 | cache_dir aufs /var/lib/qubes/vm-updates 4096 16 256 2 | maximum_object_size 4096 MB 3 | maximum_object_size_in_memory 0 4 | cache_mem 0 5 | memory_pools off 6 | 7 | http_port 127.0.0.1:8082 8 | http_access deny to_localhost 9 | http_access deny manager 10 | http_access allow localhost 11 | http_access deny all 12 | 13 | visible_hostname qubes-updates-cache 14 | forwarded_for transparent 15 | via off 16 | server_persistent_connections off 17 | shutdown_lifetime 0 seconds 18 | 19 | store_id_program /usr/lib/qubes/updates-cache-urls store-id 20 | url_rewrite_program /usr/lib/qubes/updates-cache-urls rewrite-url 21 | access_log syslog: 22 | cache_log /stderr/please/ 23 | cache_effective_user vm-updates 24 | pid_filename /run/qubes-updates-cache.pid 25 | error_directory /usr/share/qubes-updates-cache/errors 26 | -------------------------------------------------------------------------------- /usr/lib/qubes/updates-cache-urls: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [[ $1 != rewrite-url && $1 != store-id ]]; then 4 | echo "Usage: $0 rewrite-url|store-id" >&2 5 | exit 1 6 | fi 7 | 8 | 9 | # load configuration 10 | 11 | dir2=/etc/qubes-updates-cache/urls 12 | dir1=/usr/local/$dir2 13 | 14 | while IFS= read -r -d '' f; do 15 | PATH=$dir1:$dir2 source -- "$f" 16 | done < <( 17 | shopt -s nullglob 18 | for f in "$dir1"/*.bash "$dir2"/*.bash; do 19 | printf '%s\0' ${f##*/} 20 | done | sort -uz 21 | ) 22 | 23 | 24 | # preprocess rules 25 | 26 | for u in "${upgrades[@]}"; do 27 | prefix_rewrite_url_names+=( prefix_rewrite_url_"$u" ) 28 | done 29 | 30 | for p in "${prefix_rewrite_url_names[@]}"; do 31 | declare -n prefix_rewrite_url="$p" 32 | 33 | for i in ${!prefix_rewrite_url[@]}; do 34 | if [[ ${prefix_rewrite_url[i]} =~ ^[^:]+$ ]]; then 35 | # simple scheme change 36 | prefix_rewrite_url[i]+=:${prefix_store_id[i]#*:} 37 | fi 38 | done 39 | done 40 | 41 | 42 | # mangle URLs 43 | 44 | case $1 in 45 | store-id) 46 | from_prefix_names=( "${prefix_rewrite_url_names[@]}" ) 47 | to_prefix_names=( prefix_store_id ) 48 | ;; 49 | rewrite-url) 50 | from_prefix_names=( prefix_store_id ) 51 | to_prefix_names=( "${prefix_rewrite_url_names[@]}" ) 52 | ;; 53 | esac 54 | 55 | export LC_ALL=C 56 | while read -r old_url _rest_of_line; do 57 | new_url=$old_url 58 | 59 | for t in "${to_prefix_names[@]}"; do 60 | declare -n to_prefix="$t" 61 | 62 | for i in ${!to_prefix[@]}; do 63 | [[ ${to_prefix[i]} ]] || continue 64 | 65 | if [[ ${regex[i]} ]]; then 66 | if [[ $old_url =~ ${regex[i]} ]]; then 67 | new_url=${to_prefix[i]} 68 | new_url+=${BASH_REMATCH[${suffix_captured[i]}]} 69 | break 2 70 | fi 71 | else 72 | for f in "${from_prefix_names[@]}"; do 73 | declare -n from_prefix="$f" 74 | 75 | if [[ ${from_prefix[i]} && 76 | $old_url == "${from_prefix[i]}"* ]]; then 77 | new_url=${to_prefix[i]} 78 | new_url+=${old_url#"${from_prefix[i]}"} 79 | break 3 80 | fi 81 | done 82 | fi 83 | done 84 | done 85 | 86 | if [[ $old_url == "$new_url" ]]; then 87 | printf '%s: keep %s\n' "$1" "$old_url" >&2 88 | printf 'ERR\n' 89 | else 90 | printf '%s: change %s to %s\n' "$1" "$old_url" "$new_url" >&2 91 | printf 'OK %s=%s\n' "$1" "$new_url" 92 | fi 93 | done 94 | -------------------------------------------------------------------------------- /etc/qubes-updates-cache/urls/30-default-rules.bash: -------------------------------------------------------------------------------- 1 | # Debian 2 | # https://onion.debian.org 3 | 4 | regex+=( '/[dD]ebian/((dists|pool)/.*)' ) 5 | suffix_captured+=( 1 ) 6 | prefix_store_id+=( http://deb.debian.org/debian/ ) 7 | prefix_rewrite_url_https+=( https://cdn-aws.deb.debian.org/debian/ ) 8 | prefix_rewrite_url_onion+=( http://2s4yqjx5ul6okpp3f2gaunr2syex5jgbfpfvhxxbbjwnrsvbk5v3qbid.onion/debian/ ) 9 | 10 | regex+=( '' ) 11 | suffix_captured+=( '' ) 12 | prefix_store_id+=( http://security.debian.org/ ) 13 | prefix_rewrite_url_https+=( https://cdn-aws.deb.debian.org/debian-security/ ) 14 | prefix_rewrite_url_onion+=( http://5ajw6aqf3ep7sijnscdzw77t7xq4xjpsy335yb2wiwgouo7yfxtjlmid.onion/debian-security/ ) 15 | 16 | 17 | # RPM Fusion 18 | 19 | regex+=( '[/.-]rpmfusion([/.-].*)?/((nonfree|free)/.*)' ) 20 | suffix_captured+=( 2 ) 21 | prefix_store_id+=( http://download1.rpmfusion.org/ ) 22 | prefix_rewrite_url_https+=( https ) 23 | prefix_rewrite_url_onion+=( '' ) 24 | 25 | 26 | # Fedora 27 | 28 | regex+=( '[/.-][fF]edora(project)?([/.-].*)?/((releases|updates)/[0-9]+/.*)' ) 29 | suffix_captured+=( 3 ) 30 | prefix_store_id+=( http://dl.fedoraproject.org/pub/fedora/linux/ ) 31 | prefix_rewrite_url_https+=( https ) 32 | prefix_rewrite_url_onion+=( '' ) 33 | 34 | 35 | # Qubes 36 | # https://github.com/QubesOS/qubes-core-agent-linux/pull/154 37 | 38 | regex+=( '' ) 39 | suffix_captured+=( '' ) 40 | prefix_store_id+=( http://deb.qubes-os.org/ ) 41 | prefix_rewrite_url_https+=( https ) 42 | prefix_rewrite_url_onion+=( http://deb.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/ ) 43 | 44 | regex+=( '' ) 45 | suffix_captured+=( '' ) 46 | prefix_store_id+=( http://yum.qubes-os.org/ ) 47 | prefix_rewrite_url_https+=( https ) 48 | prefix_rewrite_url_onion+=( http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/ ) 49 | 50 | 51 | # Whonix 52 | # https://www.whonix.org/wiki/Forcing_.onion_on_Whonix.org 53 | 54 | regex+=( '' ) 55 | suffix_captured+=( '' ) 56 | prefix_store_id+=( http://deb.whonix.org/ ) 57 | prefix_rewrite_url_https+=( https ) 58 | prefix_rewrite_url_onion+=( http://deb.dds6qkxpwdeubwucdiaord2xgbbeyds25rbsgr73tbfpqpt4a6vjwsyd.onion/ ) 59 | 60 | 61 | # Tor 62 | # https://onion.torproject.org 63 | 64 | regex+=( '' ) 65 | suffix_captured+=( '' ) 66 | prefix_store_id+=( http://deb.torproject.org/ ) 67 | prefix_rewrite_url_https+=( https ) 68 | prefix_rewrite_url_onion+=( http://apow7mjfryruh65chtdydfmqfpj5btws7nbocgtaovhvezgccyjazpqd.onion/ ) 69 | 70 | 71 | # Google 72 | 73 | regex+=( '' ) 74 | suffix_captured+=( '' ) 75 | prefix_store_id+=( http://dl.google.com/ ) 76 | prefix_rewrite_url_https+=( https ) 77 | prefix_rewrite_url_onion+=( '' ) 78 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | qubes-updates-cache for Qubes R4.0 2 | 3 | Updating my various templates was getting a little annoying with all the 4 | redundant network traffic, so I set up a qubes-updates-cache modeled on 5 | qubes-updates-proxy, using standalone Squid (no Apache etc. involved). 6 | 7 | 8 | Security considerations 9 | 10 | 1. The remote network can distinguish if a file is requested via qubes-updates- 11 | cache (Squid) or via qubes-updates-proxy (tinyproxy). The former's user base 12 | is smaller, making attacks (where valid signatures for malicious packages 13 | can be produced somehow) more targeted even over Tor, i.e. less likely to be 14 | detected. 15 | 2. Clients can determine if a package is installed on another client on the 16 | same physical computer by requesting it and measuring cache response time. 17 | 3. (The following is also true of qubes-updates-proxy:) For Tor users, the 18 | remote network has some insight about what's installed on the same physical 19 | computer, because there is no Tor circuit isolation between update requests 20 | from different clients. This can be prevented by waiting at least 10 minutes 21 | (or sending NEWNYM) between updating different clients. 22 | 23 | 24 | Installation 25 | 26 | Create a new VM (which should be based on a distribution that packages Squid 27 | with OpenSSL support, e.g. Fedora rather than Debian), ensure it has the netvm 28 | you want, and enable the qubes-updates-cache service: 29 | 30 | [dom0] $ qvm-create --template fedora-XX --label red squidp 31 | [dom0] $ qvm-prefs squidp netvm sys-whonix # or keep the default 32 | [dom0] $ qvm-service --enable squidp qubes-updates-cache 33 | 34 | Copy this directory (containing the README you're reading) into your new 35 | VM's template, carefully inspect its contents there and: 36 | 37 | [squidp's template] # dnf install squid 38 | [squidp's template] # ./install 39 | [squidp's template] # poweroff 40 | 41 | If squidp's netvm is torifying (e.g. sys-whonix), you can transparently rewrite 42 | many repository URLs to .onion: 43 | 44 | [squidp] # mkdir -p /usr/local/etc/qubes-updates-cache/urls 45 | [squidp] # ln -s /etc/qubes-updates-cache/urls/10-onion.bash.EXAMPLE \ 46 | /usr/local/etc/qubes-updates-cache/urls/10-onion.bash 47 | 48 | If your Squid build's HTTPS support is disabled (e.g. on Debian-based 49 | distributions) you need to deactivate the rewrite-to-HTTPS rules: 50 | 51 | [squidp] # mkdir -p /usr/local/etc/qubes-updates-cache/urls 52 | [squidp] # > /usr/local/etc/qubes-updates-cache/urls/20-https.bash 53 | 54 | Edit the qubes.UpdatesProxy policy file and change all occurences of 55 | "target=sys-net" or "target=sys-whonix" to "target=squidp". 56 | 57 | [dom0] # vim /etc/qubes-rpc/policy/qubes.UpdatesProxy 58 | 59 | Finally, for Whonix gateway/workstation clients, you currently need to bypass 60 | the safety mechanism that checks for a torified update proxy: 61 | 62 | [dom0] $ qvm-service --enable whonix-ws whonix-secure-proxy 63 | 64 | That's it! Up to 4 GiB of package updates will be cached to squidp's volatile 65 | storage in /var/lib/qubes/vm-updates/. If you want to keep them across reboots: 66 | 67 | [squidp] # mkdir -p /rw/config/qubes-bind-dirs.d 68 | [squidp] # echo 'binds+=( /var/lib/qubes/vm-updates )' \ 69 | >/rw/config/qubes-bind-dirs.d/qubes-updates-cache.conf 70 | --------------------------------------------------------------------------------