summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorsommerfeld <sommerfeld@sommerfeld.dev>2026-09-17 15:05:37 +0100
committersommerfeld <sommerfeld@sommerfeld.dev>2026-09-17 15:05:37 +0100
commitf9e25d7e30205756088e7dd330b8070fb5195ae9 (patch)
treef5da3f7975e90a50c437e4c00748ee2ddf2867c8
parent2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d (diff)
downloaddotfiles-f9e25d7e30205756088e7dd330b8070fb5195ae9.tar.gz
dotfiles-f9e25d7e30205756088e7dd330b8070fb5195ae9.tar.bz2
dotfiles-f9e25d7e30205756088e7dd330b8070fb5195ae9.zip
Permit the Mattermost Snap to use GNOME Keyring
-rw-r--r--canonical/systemd/dotfiles-mattermost-keyring.path8
-rw-r--r--canonical/systemd/dotfiles-mattermost-keyring.service12
-rw-r--r--scripts/canonical-system.sh4
-rw-r--r--scripts/mattermost_keyring.py61
-rw-r--r--tests/test_mattermost_keyring.py59
5 files changed, 144 insertions, 0 deletions
diff --git a/canonical/systemd/dotfiles-mattermost-keyring.path b/canonical/systemd/dotfiles-mattermost-keyring.path
new file mode 100644
index 0000000..6d3f852
--- /dev/null
+++ b/canonical/systemd/dotfiles-mattermost-keyring.path
@@ -0,0 +1,8 @@
+[Unit]
+Description=Watch the Mattermost Snap AppArmor profile
+
+[Path]
+PathChanged=/var/lib/snapd/apparmor/profiles/snap.mattermost-desktop.mattermost-desktop
+
+[Install]
+WantedBy=multi-user.target
diff --git a/canonical/systemd/dotfiles-mattermost-keyring.service b/canonical/systemd/dotfiles-mattermost-keyring.service
new file mode 100644
index 0000000..fd75f86
--- /dev/null
+++ b/canonical/systemd/dotfiles-mattermost-keyring.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Allow Mattermost Snap to use GNOME Keyring
+After=snapd.apparmor.service snapd.service apparmor.service
+Before=display-manager.service
+ConditionPathExists=/var/lib/snapd/apparmor/profiles/snap.mattermost-desktop.mattermost-desktop
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/python3 /usr/local/lib/dotfiles/mattermost_keyring.py
+
+[Install]
+WantedBy=multi-user.target
diff --git a/scripts/canonical-system.sh b/scripts/canonical-system.sh
index e6f0825..cd85879 100644
--- a/scripts/canonical-system.sh
+++ b/scripts/canonical-system.sh
@@ -13,6 +13,10 @@ sudo apparmor_parser --skip-kernel-load --skip-cache canonical/apparmor/dotfiles
sudo install -m 644 canonical/apparmor/dotfiles-nix /etc/apparmor.d/dotfiles-nix
sudo apparmor_parser --replace /etc/apparmor.d/dotfiles-nix
sudo snap connect thunderbird:gpg-keys
+sudo install -D -m 644 scripts/mattermost_keyring.py /usr/local/lib/dotfiles/mattermost_keyring.py
+sudo install -m 644 canonical/systemd/dotfiles-mattermost-keyring.{service,path} /etc/systemd/system/
+sudo systemctl daemon-reload
+sudo systemctl enable --now dotfiles-mattermost-keyring.path dotfiles-mattermost-keyring.service
systemctl --user daemon-reload
systemctl --user enable --now gpg-agent.socket gpg-agent-ssh.socket podman.socket
echo 'Existing GPG agent processes keep their executable until the next login.'
diff --git a/scripts/mattermost_keyring.py b/scripts/mattermost_keyring.py
new file mode 100644
index 0000000..a54ed6d
--- /dev/null
+++ b/scripts/mattermost_keyring.py
@@ -0,0 +1,61 @@
+"""Permit the Mattermost Snap to use the desktop Secret Service."""
+
+import os
+import re
+import subprocess
+import tempfile
+from pathlib import Path
+
+PROFILE = Path(
+ "/var/lib/snapd/apparmor/profiles/snap.mattermost-desktop.mattermost-desktop"
+)
+MARKER = "# dotfiles: Mattermost Secret Service access"
+RULES = """
+dbus (receive, send)
+ bus=session
+ path=/org/freedesktop/secrets{,/**}
+ interface=org.freedesktop.DBus.*
+ peer=(label=unconfined),
+dbus (receive, send)
+ bus=session
+ path=/org/freedesktop/secrets{,/**}
+ interface=org.freedesktop.Secret.{Collection,Item,Prompt,Service,Session}
+ peer=(label=unconfined),
+"""
+
+
+def patch_profile(text: str) -> str:
+ profiles = re.findall(r'^profile "([^"]+)"', text, re.MULTILINE)
+ if profiles != [PROFILE.name] or not text.rstrip().endswith("}"):
+ raise ValueError("Unexpected Mattermost AppArmor profile format.")
+ if MARKER in text:
+ return text
+ return text.rstrip()[:-1] + MARKER + "\n" + RULES + "}\n"
+
+
+def install_profile(text: str) -> None:
+ with tempfile.TemporaryDirectory(dir=PROFILE.parent) as directory:
+ target = Path(directory) / PROFILE.name
+ target.write_text(text)
+ subprocess.run(
+ ["apparmor_parser", "--skip-kernel-load", "--skip-cache", str(target)],
+ check=True,
+ )
+ target.chmod(PROFILE.stat().st_mode & 0o777)
+ os.replace(target, PROFILE)
+
+
+def main() -> None:
+ if os.geteuid() != 0:
+ raise SystemExit("Run this command as root.")
+ original = PROFILE.read_text()
+ patched = patch_profile(original)
+ if patched != original:
+ install_profile(patched)
+ subprocess.run(
+ ["apparmor_parser", "--replace", "--skip-cache", str(PROFILE)], check=True
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/test_mattermost_keyring.py b/tests/test_mattermost_keyring.py
new file mode 100644
index 0000000..93cc96d
--- /dev/null
+++ b/tests/test_mattermost_keyring.py
@@ -0,0 +1,59 @@
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+from unittest.mock import patch
+
+from scripts import mattermost_keyring
+
+
+class MattermostKeyringTests(unittest.TestCase):
+ def test_adds_rules_only_once(self):
+ original = 'profile "snap.mattermost-desktop.mattermost-desktop" {\n}\n'
+ patched = mattermost_keyring.patch_profile(original)
+ self.assertIn("interface=org.freedesktop.Secret.", patched)
+ self.assertEqual(mattermost_keyring.patch_profile(patched), patched)
+
+ def test_rejects_other_profiles(self):
+ with self.assertRaises(ValueError):
+ mattermost_keyring.patch_profile('profile "snap.other" {\n}\n')
+
+ def test_rejects_incomplete_profile(self):
+ with self.assertRaises(ValueError):
+ mattermost_keyring.patch_profile(
+ 'profile "snap.mattermost-desktop.mattermost-desktop" {\n'
+ )
+
+ def test_rejects_multiple_profiles(self):
+ with self.assertRaises(ValueError):
+ mattermost_keyring.patch_profile(
+ 'profile "snap.mattermost-desktop.mattermost-desktop" {\n}\n'
+ 'profile "snap.other" {\n}\n'
+ )
+
+ def test_preserves_snap_permissions(self):
+ original = (
+ 'profile "snap.mattermost-desktop.mattermost-desktop" '
+ "flags=(attach_disconnected,mediate_deleted) {\n /example r,\n}\n"
+ )
+ patched = mattermost_keyring.patch_profile(original)
+ self.assertTrue(patched.startswith(original[:-2]))
+ self.assertNotIn("kwallet", patched)
+ self.assertNotIn("complain", patched)
+
+ def test_parser_failure_leaves_installed_profile_unchanged(self):
+ with tempfile.TemporaryDirectory() as directory:
+ profile = Path(directory) / mattermost_keyring.PROFILE.name
+ profile.write_text("original")
+ with (
+ patch.object(mattermost_keyring, "PROFILE", profile),
+ patch.object(
+ mattermost_keyring.subprocess,
+ "run",
+ side_effect=subprocess.CalledProcessError(1, "apparmor_parser"),
+ ),
+ self.assertRaises(subprocess.CalledProcessError),
+ ):
+ mattermost_keyring.install_profile("invalid")
+ self.assertEqual(profile.read_text(), "original")
+ self.assertEqual(list(Path(directory).iterdir()), [profile])